# ProjectSight MCP Server
A scalable, maintainable MCP (Model Context Protocol) server that provides access to the ProjectSight API via **425 tools** across **48 domains**βincluding portfolios, projects, action items, RFIs, submittals, contracts, budgets, applications for payment, change orders, daily reports, drawings, files, meetings, and more.
**This server has been refactored into a modular, teachable structure** that demonstrates best practices for building MCP servers. It's designed to be both production-ready and an excellent learning resource.
## π― Features
- **425 tools across 48 domains**: Full ProjectSight API coverageβActionItems, RFIs, Submittals, Projects, Portfolios, Contracts, Budgets, Applications for Payment, Change Orders (potential/prime/sub), Daily Reports, Field Work Directives, Drawings, Drawing Sets, Files/Folders, Meetings, Notices, Safety Notices, Punch Lists, Issues, Checklists, Forecasts, Job Costs, Purchase Orders, Invoicing, ERP read-only, Users, Roles, Companies, Contacts, and more.
- **Intelligent gateway**: By default the server exposes a single **`projectsight`** tool with intent matching and **35+ named capabilities** (multi-tool workflows) defined in `mcp/yaml/capabilities.yaml`. Set `MCP_GATEWAY_ONLY=0` to expose all 425 tools.
- **Server policy**: Deletes never run by default; optional approval for create/update via `mcp/yaml/policy.yaml` and env overrides (`MCP_DELETE_POLICY`, `MCP_REQUIRE_APPROVAL_FOR_MUTATIONS`).
- **OAuth2 Authentication**: Automatic token management with caching and refresh; **client credentials** (STDIO) or **On-Behalf token exchange** (e.g. Agent Studio).
- **Modular Architecture**: Clean separation of concerns, registry-driven gateway, easy to extend.
- **Debug Tools**: Test connection, debug tokens, get context requirements, test different scopes.
## π Project Structure
```
projectsight-mcp/
βββ mcp/
β βββ main.py # Entry point (STDIO or --http)
β βββ config.py # Env config; portfolio/scope resolution
β βββ request_context.py # Per-request context (actor token, X-* headers)
β βββ auth.py # OAuth2 client credentials + On-Behalf exchange
β βββ client.py # ProjectSight API client (retry, token refresh)
β βββ utils.py # Helpers (e.g. resolve_project)
β βββ registry.py # Load tool_registry.yaml; HANDLER_REGISTRY
β βββ policy.py # Delete/mutation policy from policy.yaml + env
β βββ yaml/
β β βββ policy.yaml # delete_policy, mutation_approval
β β βββ tool_registry.yaml # Tool metadata for gateway (425 tools)
β β βββ capabilities.yaml # Named multi-tool workflows (35+ capabilities)
β βββ scripts/
β β βββ build_registry.py # Generate tool_registry.yaml from tool modules
β βββ docs/
β β βββ TOOL_STANDARD.md # Docstring and registry standards
β β βββ TOOL_REGISTRY_MAINTENANCE.md # How to maintain/regenerate registry
β βββ tools/ # MCP tools organized by domain
β β βββ __init__.py # register_tools(); gateway-only vs all-tools
β β βββ gateway.py # projectsight(user_request, context, prefer_discovery)
β β βββ debug.py # test_connection, debug_token, get_mcp_context_requirements
β β βββ projects.py, portfolio.py
β β βββ action_items.py, rfis.py, submittals.py, workflow.py, workflow_states.py
β β βββ application_for_payment.py, budget.py, budget_code_structure.py, budget_group.py, budget_snapshot.py
β β βββ change_order_request.py, potential_co.py, prime_contract_co.py, sub_contract_co.py
β β βββ checklist.py, company.py, contact.py, contract.py, contract_invoice.py
β β βββ daily_report.py, drawing.py, drawing_set.py, erp_read_only.py
β β βββ field_work_directive.py, file.py, folder.py, forecast.py, general_invoice.py
β β βββ issue.py, job_costs.py, lookup_list.py, meeting.py, notice.py
β β βββ photo.py, po_catalog.py, purchase_order.py, records.py, report_generator.py
β β βββ role.py, safety_notice.py, submittal_package.py, transmittal.py, user.py
β β βββ punch_list.py
β βββ tests/
β βββ test_gateway_intent.py # Registry + intent-matching tests
βββ README.md
βββ pyproject.toml
βββ .env # Create this; not committed
```
Tool modules register with the MCP instance and with `HANDLER_REGISTRY` so the gateway can execute them by name.
## π Quick Start
### 1. Install Dependencies
```bash
pip install fastmcp python-dotenv aiohttp uvicorn pyyaml
```
Or using a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install fastmcp python-dotenv aiohttp uvicorn pyyaml
```
(Pyyaml is used by the registry and policy modules.)
### 2. Configure Credentials
Create a `.env` file in the `mcp/` directory (or project root) with your ProjectSight API credentials:
```env
APPLICATION_NAME=your_app_name
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
PROJECTSIGHT_API_URL=https://cloud.api.trimble.com/projectsight/us1/1.0
PROJECTSIGHT_SCOPE=ProjectSight_-_US1
# Optional: PORTFOLIO_ID=your_portfolio_guid (UUID). If unset, the server discovers a default via GET /accounts and GET /accounts/{accountId}/portfolios at startup.
# Optional: for stage or other envs use TRIMBLE_TOKEN_URL=https://stage.id.trimblecloud.com/oauth/token
```
**Note on PORTFOLIO_ID:** `PORTFOLIO_ID` is **optional**. If unset, the server calls the ProjectSight API (`GET /accounts` and `GET /accounts/{accountId}/portfolios`) at startup and uses the first (or only) portfolio found. You can also discover accounts and portfolios with **get_accounts** and **get_portfolios_for_account**, then set `PORTFOLIO_ID` in .env to a Portfolio GUID (UUID) or pass `portfolio_guid` on tool calls. The API base URL is unchanged (`PROJECTSIGHT_API_URL`); all requests, including account/portfolio discovery, use it.
**Getting Credentials:**
1. Sign in to [API Cloud](https://console.trimble.com) with your Trimble account
2. On the Discover API page, select **ProjectSight** or **ProjectSight-EU**
3. Select **Subscriptions** and subscribe your application
4. Select **Get Key** and copy:
- Application Name
- Consumer Key (CLIENT_ID)
- Consumer Secret (CLIENT_SECRET)
For more information, email `ProjectSightAPISupport@trimble.com` to request API access.
**Note on Scope**: The `PROJECTSIGHT_SCOPE` should match your API subscription region:
- `ProjectSight_-_US1` for US region
- `ProjectSight_-_EU1` for EU region
- `ProjectSight_-_US2` for Azure US region
### 3. Run the Server
Run from the **`mcp/`** directory so that `.env` and imports resolve correctly (or set `PYTHONPATH` and ensure `.env` is loaded from the right place).
**STDIO Mode (default for MCP clients):**
```bash
cd mcp
python main.py
```
**HTTP Streaming Mode:**
```bash
cd mcp
python main.py --http
```
**With custom host/port:**
```bash
# Windows PowerShell
$env:MCP_HOST="0.0.0.0"
$env:MCP_PORT="8000"
python main.py --http
# Linux/Mac
export MCP_HOST=0.0.0.0
export MCP_PORT=8000
python main.py --http
```
**Gateway-only mode (default):**
The server defaults to exposing only the intelligent `projectsight` gateway tool so the agent is not overwhelmed by hundreds of tools. No configuration needed. To expose all tools instead (e.g. for debugging), set `MCP_GATEWAY_ONLY=0` before starting. See "Intelligent Gateway Tool" below.
**Port already in use (10048):** If you see "port already in use" or error 10048 when running `python main.py --http`, use another port: set `MCP_PORT=8001` (PowerShell: `$env:MCP_PORT="8001"`), then start the server again and use `http://localhost:8001/mcp` in your client. To free port 8000 on Windows: run `netstat -ano | findstr :8000`, note the PID (last column), then `taskkill /PID <pid> /F`.
## π§ MCP Client Configuration
### STDIO Mode (Cursor, Claude Desktop, etc.)
```json
{
"mcpServers": {
"projectsight": {
"command": "python",
"args": ["C:\\Users\\cforey\\Desktop\\projectsight-mcp\\mcp\\main.py"],
"env": {
"APPLICATION_NAME": "your_app_name",
"CLIENT_ID": "your_client_id",
"CLIENT_SECRET": "your_client_secret",
"PORTFOLIO_ID": "your-portfolio-guid",
"MCP_GATEWAY_ONLY": "1"
}
}
}
}
```
**Note**: You can also use a `.env` file instead of setting environment variables in the config. The script will automatically load variables from a `.env` file in the `mcp/` directory. `PORTFOLIO_ID` can be omitted; the server will discover a default portfolio at startup. `MCP_GATEWAY_ONLY=1` is the default (single-tool mode); including it in `env` makes the behavior explicit for Cursor and other clients.
### HTTP Mode
For MCP clients that support HTTP transport, configure the connection URL:
```
http://localhost:8000/mcp
```
### On behalf of actor token (Agent Studio)
When the MCP is used from **Trimble Agent Studio** (or any client that sends the signed-in user's Trimble ID token), you can configure the MCP with **"On behalf of actor token"** instead of static credentials. The client sends the user's TID token as `Authorization: Bearer <token>` on each request. The MCP then exchanges that token for a ProjectSight-scoped token via Trimble Identity's **On Behalf / Token Exchange** grant and uses it for API calls, so requests run as that user.
**Token format:** The Bearer token must be a **JWT** (e.g. Trimble ID token). The MCP detects JWT shape and sends the correct `subject_token_type` for the exchange. If you see an error like `subject_token type 'urn:ietf:params:oauth:token-type:access_token' not supported`, ensure the client sends a JWT in `Authorization: Bearer`, not an opaque access token.
**Setup in Agent Studio:**
- **Authentication:** Select **On behalf of actor token**.
- **URL:** Your MCP endpoint (e.g. `https://your-mcp-host/mcp`). The MCP must be reachable over HTTPS when used from Studio.
- **Scopes (informational):** Required scopes for the user's token typically include:
- `openid`
- ProjectSight scope for your region: `ProjectSight_-_US1` (US), `ProjectSight_-_EU1` (EU), or `ProjectSight_-_US2` (Azure US).
Confirm exact scope names with [Trimble Identity](https://developer.trimble.com/docs/authentication/concepts/trimble-identity/) and [API Endpoints](https://developer.trimble.com/docs/authentication/reference/api) for your environment (stage vs prod).
**Server-side:** The MCP still needs `CLIENT_ID` and `CLIENT_SECRET` in `.env` (or environment) to perform the On Behalf token exchange. Set `APPLICATION_NAME`, `PROJECTSIGHT_SCOPE`, `PORTFOLIO_ID`, and `PROJECTSIGHT_API_URL` as defaults; they can be overridden per request via headers (see below).
**Optional per-request headers (when a gateway or client sends them):**
- `X-Portfolio-Id` β Portfolio GUID for the request (overrides `PORTFOLIO_ID` for that request).
- `X-API-Base-URL` β ProjectSight API base URL (e.g. for a different region).
- `X-ProjectSight-Scope` β Scope used for token exchange (e.g. `ProjectSight_-_US1`).
- `X-Application-Name` β Application name for token/scope.
If these headers are not sent, the MCP uses `.env` defaults and tool parameters (e.g. `portfolio_guid`) as today.
**Fallback:** When no `Authorization: Bearer` token is present (e.g. STDIO or "None" auth in Studio), the MCP uses **client credentials** from `.env` as before.
**Troubleshooting On Behalf exchange:**
| Error | Cause | What to do |
|-------|--------|------------|
| `JWT error: Signature verification failed` | The Bearer token was not issued by (or cannot be verified by) the same Trimble Identity environment the MCP is using. | Ensure **TRIMBLE_TOKEN_URL** in `.env` matches the IdP that issued the token. For **stage** (e.g. studio.stage.trimble-ai.com) use the stage token URL (e.g. `https://stage.id.trimblecloud.com/oauth/token` or per Trimble docs); for **production** use `https://id.trimble.com/oauth/token`. The client must obtain the token from that same environment. |
| `Caller is not the intended audience of subject token` | The JWT was issued for a different application; Trimble Identity will not exchange it for a token for this MCP's CLIENT_ID. | Ensure the MCP's **CLIENT_ID** (in `.env`) is the application the user signs into, or that the token requested by the client (e.g. Agent Studio) has **audience** (or resource) that includes this MCP's application. Configure the client/IdP so the subject token's `aud` includes the MCP's CLIENT_ID. |
| `subject_token type 'access_token' not supported` | IdP expects a JWT. | The MCP now sends JWT type when the token looks like a JWT. Ensure the client sends a JWT in `Authorization: Bearer`. |
**Automatic fallback when On Behalf fails:** If On Behalf token exchange fails (e.g. signature verification or intended audience), the MCP **automatically falls back to client credentials** so API requests can still be made. Set **PORTFOLIO_ID** in `.env` if discovery does not find a portfolio or you need a specific portfolio. Requests then run with the application identity (not the signed-in user).
**Workaround when On Behalf is not configured:** Omit the Bearer token (use client credentials only), set **PORTFOLIO_ID** in `.env`, and the MCP will use the cached client_credentials token for discovery and all tools.
## π§ Intelligent Gateway Tool (Single-Tool Mode)
By default (or when **MCP_GATEWAY_ONLY=1**), the server exposes a single tool **`projectsight`** so the agent is not overwhelmed by hundreds of tools. Set **MCP_GATEWAY_ONLY=0** to expose all tools.
**Named capabilities:** The gateway can run **named capabilities** (e.g. `project_overview`, `contract_and_budget_summary`, `quality_and_issues`) that execute a fixed sequence of tools with shared context. The list is in [mcp/yaml/capabilities.yaml](mcp/yaml/capabilities.yaml).
**Parameters:**
- **user_request** (required): Natural language request (e.g. "List submittals for Downtown project", "Get projects", "Test connection").
- **context** (optional): Dict with known context from prior turns: `portfolio_guid`, `project_id`, `project_name`, `contract_id`, etc.
- **prefer_discovery** (optional): If `true`, returns only a plan (which tools would run) without executing.
**Responses:**
- **action: "need_more_info"** β Ask the user `questions_for_user`, then call again with `context` updated (e.g. user says "Downtown" β send `context: { "project_name": "Downtown" }`).
- **action: "plan"** β When `prefer_discovery=true`, returns the steps that would run (no execution).
- **action: "result"** β The result from the executed tool(s).
- **action: "policy_blocked"** β A delete was requested; this server does not run delete commands (delete actions require triple check).
- **action: "approval_required"** β A create/update was requested and the server is configured to require approval; call again with the same request and `context` plus `approved: true` or `confirm_mutation: true` to execute.
- **action: "error"** β Error and suggestion.
**Server policy (no deletes; optional approval for create/update):**
- **Deletes:** The server does not execute delete commands. Delete requests return `action: "policy_blocked"` with a message that delete actions require triple check; use the ProjectSight UI or API for deletions.
- **Create/Update:** You can require approval before running create/update tools by setting `MCP_REQUIRE_APPROVAL_FOR_MUTATIONS=1`. Then the first call returns `action: "approval_required"` with a plan; the agent or user can confirm by calling again with the same context plus `approved: true` or `confirm_mutation: true`. Policy is configured in `mcp/yaml/policy.yaml` and overridden by `MCP_DELETE_POLICY` and `MCP_REQUIRE_APPROVAL_FOR_MUTATIONS` in `.env`.
**Recommended workflow for the agent:**
1. User sends a message.
2. Agent calls `projectsight(user_request=user_message, context={})`.
3. If response is **need_more_info** β agent asks the user the returned questions, then calls `projectsight(user_request=..., context={ ... user answers ... })`.
4. If response is **plan** (with `prefer_discovery=true`) β agent can confirm with user, then call again without `prefer_discovery` to execute.
5. If response is **result** β agent presents the result to the user.
6. If response is **error** β agent shows the error and suggestion.
**Context continuity (multi-turn conversations):** The gateway is stateless: each call receives only the `user_request` and `context` for that call. Successful responses include **`resolved_context`** (e.g. `project_id`, `project_name`, `portfolio_guid`, and when relevant record IDs such as `rfi_id`). **Clients should retain this `resolved_context` and send it as the `context` argument on the next `projectsight` call** so that follow-up requests (e.g. "change the due date to Feb 19" after listing RFIs) work without the user re-specifying project or RFI. When the user refers to a specific item from a prior result (e.g. "that one", "RFI 005"), the client should add the corresponding ID (e.g. `rfi_id: 5`) to the context it sends.
No multi-tool sequencing is required by the agent; the gateway resolves project/portfolio context and runs the right internal tools server-side. Tool metadata is in `mcp/yaml/tool_registry.yaml`; handlers are registered at startup in `HANDLER_REGISTRY` for execution.
## π Available Tools
The server provides **425 tools** across **48 domains**. Full tool list and metadata: [mcp/yaml/tool_registry.yaml](mcp/yaml/tool_registry.yaml). Named multi-tool capabilities (e.g. `project_overview`, `contract_and_budget_summary`, `quality_and_issues`): [mcp/yaml/capabilities.yaml](mcp/yaml/capabilities.yaml). When using the gateway, call **get_mcp_context_requirements()** for required context and tools grouped by domain.
**Domains (48):** action_items, application_for_payment, budget, budget_code_structure, budget_group, budget_snapshot, change_order_request, checklist, company, contact, contract, contract_invoice, daily_report, debug, drawing, drawing_set, erp_read_only, field_work_directive, file, folder, forecast, general_invoice, issue, job_costs, lookup_list, meeting, notice, photo, po_catalog, portfolio, potential_co, prime_contract_co, projects, punch_list, purchase_order, records, report_generator, rfis, role, safety_notice, sub_contract_co, submittal_package, submittals, transmittal, user, workflow, workflow_states.
**Common tools (when not using gateway-only mode):** get_projects, list_action_items, list_rfis, list_submittals, test_connection, get_mcp_context_requirements. Use the corresponding list/get tools to discover IDs (e.g. list_contracts, list_budgets) when a tool asks for `project_id`, `contract_id`, etc. Portfolio-level tools use `portfolio_guid` from the parameter or `PORTFOLIO_ID` from `.env` (must be a Portfolio GUID/UUID).
## ποΈ Architecture & Best Practices
This MCP server uses a **scalable, maintainable structure** with a registry-driven gateway.
### Architecture and data flow
- **Startup:** `main.py` loads Config, creates Auth and ProjectSightClient, and calls `register_tools(mcp, client)`. In `tools/__init__.py`, if `MCP_GATEWAY_ONLY=1` (default), internal tool modules register with a no-op MCP so they don't appear as tools to the client, but they still register handlers in `HANDLER_REGISTRY`. The gateway always registers the single `projectsight` tool on the real MCP.
- **Gateway flow:** The user calls `projectsight(user_request, context?, prefer_discovery?)` β keyword-based intent matching against `mcp/yaml/tool_registry.yaml` and `mcp/yaml/capabilities.yaml` β resolve `portfolio_guid`/`project_id` (and project name) via `utils.resolve_project` and Config/request context β policy check (delete blocked; mutation approval if required) β execute handler(s) from `HANDLER_REGISTRY` β return `need_more_info` | `plan` | `result` | `policy_blocked` | `approval_required` | `error`.
- **Request context (HTTP):** `request_context.py` holds per-request actor token and overrides (`X-Portfolio-Id`, `X-API-Base-URL`, `X-ProjectSight-Scope`, `X-Application-Name`). Auth and Config use these when present (e.g. Agent Studio).
- **Policy:** `policy.py` reads `mcp/yaml/policy.yaml`; env vars `MCP_DELETE_POLICY` and `MCP_REQUIRE_APPROVAL_FOR_MUTATIONS` override. Deletes are never run by default; create/update can require `context.approved` or `context.confirm_mutation`.
```mermaid
flowchart LR
User --> projectsight
projectsight --> IntentMatch
IntentMatch --> ContextResolve
ContextResolve --> PolicyCheck
PolicyCheck --> Handlers
Handlers --> Response
```
- **Modular Design**: Each component in its own file with clear responsibilities.
- **Separation of Concerns**: Configuration, authentication, API client, registry, policy, and tools are separated.
- **Organized Tools**: Tools grouped by domain; gateway uses registry and capabilities for intent matching.
### π Documentation
- **[TOOL_STANDARD.md](mcp/docs/TOOL_STANDARD.md)**: Docstring and registry standards for tools; body/Dict expectations.
- **[TOOL_REGISTRY_MAINTENANCE.md](mcp/docs/TOOL_REGISTRY_MAINTENANCE.md)**: How to maintain or regenerate `mcp/yaml/tool_registry.yaml` (e.g. run `python scripts/build_registry.py` from `mcp/`).
### Testing
Gateway intent-matching tests live in `mcp/tests/`. Run them from the `mcp` directory:
```bash
cd mcp
python -m unittest tests.test_gateway_intent -v
```
Tests cover registry loading, intent matching for common phrases (e.g. "list submittals", "get projects"), and capability-hint summary.
### π Learning Resource
This structure is designed to be a **teaching example** - use it as a reference when building your own MCP servers! The code demonstrates:
- β
Single Responsibility Principle
- β
Separation of Concerns
- β
Dependency Injection
- β
DRY (Don't Repeat Yourself)
- β
Clear Naming Conventions
- β
Comprehensive Documentation
- β
Type Hints
- β
Error Handling Patterns
## π Authentication
The server uses OAuth2 client credentials flow with Trimble Identity:
- Tokens are automatically cached and refreshed
- Token cache is stored at `~/.cache/projectsight/token_cache.json`
- Tokens are refreshed automatically when expired
## π Important Notes
- **Portfolio ID**: Optional in .env. When unset, the server discovers a default portfolio at startup via the accounts/portfolios API. Use **get_accounts** and **get_portfolios_for_account** to list accounts and portfolios, then set `PORTFOLIO_ID` to a Portfolio GUID (UUID) or pass `portfolio_guid` to tools. If `PORTFOLIO_ID` is an integer (Account ID), you must pass `portfolio_guid` per request or set `PORTFOLIO_ID` to a UUID.
- **Server behavior (env):** `MCP_GATEWAY_ONLY` (default `1` = only `projectsight` tool; `0` = expose all 425 tools). `MCP_DELETE_POLICY` (default `never_run`; deletes are never executed). `MCP_REQUIRE_APPROVAL_FOR_MUTATIONS` (set to `1` to require `context.approved` or `context.confirm_mutation` before create/update tools run). Optional: `MCP_REQUEST_TIMEOUT_SECONDS`, `MCP_CLIENT_RETRY_COUNT`.
- **Project Lookup**: Many tools support finding projects by name (case-insensitive, partial match) if you don't know the project ID.
- **RFI Creation**: The `create_or_update_rfi` tool is highly flexible and will:
- Auto-detect workflow states from existing RFIs
- Auto-generate RFI numbers
- Normalize dates from various formats ("today", "tomorrow", "2026-01-26", etc.)
- Map importance text ("high", "normal", "low") to importance IDs
- Use existing RFI settings as defaults when appropriate
## π Exposing via Public URL (Tunneling)
To make your local server accessible via a public URL, use a tunneling service:
### Option 1: ngrok (Recommended)
1. **Install ngrok on Windows**:
```powershell
winget install ngrok.ngrok
```
2. **Start your MCP server**:
```bash
cd mcp
python main.py --http
```
3. **Create a tunnel** (in a separate terminal):
```bash
ngrok http 8000
```
4. **Use the public URL**: ngrok will provide a public URL like `https://abc123.ngrok-free.dev`.
The MCP endpoint is at `/mcp`:
```
https://abc123.ngrok-free.dev/mcp
```
### Option 2: Cloudflare Tunnel (cloudflared)
1. **Install cloudflared**: Download from [developers.cloudflare.com](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/)
2. **Start your MCP server**:
```bash
cd mcp
python main.py --http
```
3. **Create a tunnel** (in a separate terminal):
```bash
cloudflared tunnel --url http://localhost:8000
```
4. **Use the public URL**: Cloudflare will provide a public URL like `https://random-subdomain.trycloudflare.com`. Your MCP endpoint will be:
```
https://random-subdomain.trycloudflare.com/mcp
```
## π Security Note
When exposing your server publicly, consider:
- Adding authentication/API keys if your MCP server handles sensitive data
- Using HTTPS (all tunneling services above provide HTTPS)
- Limiting access to specific IPs if possible
- Monitoring usage and rate limiting
## π Rate Limits
Based on updated guidance from Trimble Cloud, you don't need to pass the x-API-key to the trimblepaas.com endpoints. If you do pass it, there is a limit of 50 requests per second.
## π API Documentation
For detailed API documentation, refer to:
- [ProjectSight API Documentation](https://raw.githubusercontent.com/trimble-oss/projectsight-api-samples/main/ProjectSight-v1.json)
## π Migration from Old Structure
If you were using the old `projectsight.py` file directly:
1. **Update command**: Use `python main.py` instead of `python projectsight.py`
2. **Configuration**: Same `.env` file format (place in `mcp/` directory)
3. **MCP Client Config**: Update path to point to `mcp/main.py`
4. **Tools**: All tools work the same, just organized better
The old `projectsight.py` file is still available for reference but the new modular structure is recommended.
## π οΈ Extending the Server
- **New tools** live under `mcp/tools/<domain>.py`. Each tool module defines `register(mcp, handler_registry)` and registers both the FastMCP tool and an async handler: `handler_registry[name] = my_async_handler`. The gateway executes tools by name via `get_handler(name)` from the registry.
- **Gateway intent matching:** Add (or regenerate) the tool's entry in `mcp/yaml/tool_registry.yaml` (name, description, domain, required_context, optional_context, keywords; optionally examples, follow_ups). Run `python scripts/build_registry.py` from the `mcp/` directory to regenerate the registry from docstrings; see [TOOL_REGISTRY_MAINTENANCE.md](mcp/docs/TOOL_REGISTRY_MAINTENANCE.md).
- **Wiring:** Add the new module to the imports and `register(...)` calls in `mcp/tools/__init__.py`. Optional: if the tool is part of a multi-step workflow, add or extend an entry in `mcp/yaml/capabilities.yaml`.
Example pattern:
```python
# mcp/tools/my_domain.py
from fastmcp import FastMCP
_client = None
def register(mcp: FastMCP, handler_registry: dict):
@mcp.tool()
async def my_tool(portfolio_guid: str, param: str) -> dict:
"""One-line summary. Args: portfolio_guid: ... param: ... Returns: ..."""
return await _run_my_tool(portfolio_guid, param)
handler_registry["my_tool"] = _run_my_tool # gateway executes by name
async def _run_my_tool(portfolio_guid: str, param: str) -> dict:
result = await _client.get(f"/{portfolio_guid}/endpoint/{param}")
return {"result": result}
def set_client(client):
global _client
_client = client
```
## π License
This project is provided as-is for use with the ProjectSight API.
---
**Built with best practices in mind** - Use this structure as a reference for building your own MCP servers! π