# MCP Integrations Package
**Created**: 2026-01-04
**Status**: Phase 4 Complete (Tool Search + Template Integration)
**Package Location**: `/home/jez/Documents/mcp/mcp-integrations/`
## Overview
A shared npm package containing reusable MCP tools, resources, and prompts for backend service integrations. Each MCP server imports what it needs - no framework, just tested integrations.
**Package**: `@jezweb/mcp-integrations`
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ MCP Server (per client) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Template │ │ Config │ │ Custom │ │
│ │ (auth, UI) │ │ (secrets) │ │ Tools │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ import { calendarTools } from '@jezweb/mcp-integrations' │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ @jezweb/mcp-integrations │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Google │ │ Google │ │ Google │ │ Xero │ │
│ │ Calendar │ │ Gmail │ │ Sheets │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Package Structure
```
@jezweb/mcp-integrations/
├── src/
│ ├── index.ts # Main exports
│ ├── types.ts # Shared types (ToolDefinition, etc.)
│ ├── helpers.ts # defineTool, defineResource, definePrompt
│ │
│ ├── google/
│ │ ├── index.ts # Re-exports all Google integrations
│ │ ├── auth.ts # Google-specific auth helpers
│ │ │
│ │ ├── calendar/
│ │ │ ├── index.ts # Exports tools, resources, prompts, meta
│ │ │ ├── tools/
│ │ │ │ ├── index.ts
│ │ │ │ ├── events.ts # listEvents, createEvent, updateEvent, deleteEvent
│ │ │ │ └── calendars.ts # listCalendars, getCalendar
│ │ │ ├── resources.ts
│ │ │ └── prompts.ts
│ │ │
│ │ ├── gmail/
│ │ │ ├── index.ts
│ │ │ ├── tools/
│ │ │ │ ├── messages.ts # listMessages, getMessage, sendMessage
│ │ │ │ ├── threads.ts # listThreads, getThread
│ │ │ │ ├── labels.ts # listLabels, createLabel
│ │ │ │ └── drafts.ts # createDraft, sendDraft
│ │ │ ├── resources.ts
│ │ │ └── prompts.ts
│ │ │
│ │ ├── sheets/
│ │ └── drive/
│ │
│ ├── xero/
│ │ ├── index.ts
│ │ ├── tools/
│ │ │ ├── invoices.ts
│ │ │ ├── contacts.ts
│ │ │ └── accounts.ts
│ │ ├── resources.ts
│ │ └── prompts.ts
│ │
│ └── utils/
│ └── authorized-fetch.ts # Token refresh pattern
│
├── package.json
├── tsconfig.json
└── README.md
```
## Tool Definition Pattern
Tools include metadata for Tool Search (Anthropic standard):
```typescript
// src/helpers.ts
import { z } from 'zod';
export interface ToolDefinition<T extends z.ZodType = z.ZodType> {
name: string;
description: string;
parameters: T;
handler: (params: z.infer<T>, ctx: ToolContext) => Promise<ToolResult>;
// Tool Search metadata
category: string;
tags: string[];
alwaysVisible?: boolean; // Default: false
}
export interface ToolContext {
authorizedFetch: (url: string, init?: RequestInit) => Promise<Response>;
userId?: string;
// ... other context
}
export interface ToolResult {
content: Array<{ type: 'text'; text: string }>;
isError?: boolean;
}
export function defineTool<T extends z.ZodType>(def: ToolDefinition<T>): ToolDefinition<T> {
return {
alwaysVisible: false,
...def,
};
}
```
## Example Integration: Google Calendar
```typescript
// src/google/calendar/tools/events.ts
import { z } from 'zod';
import { defineTool } from '../../../helpers';
export const listEvents = defineTool({
name: 'google_calendar_list_events',
description: 'List upcoming events from Google Calendar. Returns event titles, times, and attendees.',
category: 'calendar',
tags: ['google', 'calendar', 'events', 'schedule', 'meetings', 'read'],
alwaysVisible: true, // Common operation
parameters: z.object({
calendarId: z.string().default('primary').describe('Calendar ID'),
maxResults: z.number().min(1).max(100).default(10).describe('Max events to return'),
timeMin: z.string().optional().describe('Start time (ISO 8601)'),
timeMax: z.string().optional().describe('End time (ISO 8601)'),
}),
handler: async (params, ctx) => {
const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${params.calendarId}/events`);
url.searchParams.set('maxResults', String(params.maxResults));
url.searchParams.set('singleEvents', 'true');
url.searchParams.set('orderBy', 'startTime');
if (params.timeMin) url.searchParams.set('timeMin', params.timeMin);
if (params.timeMax) url.searchParams.set('timeMax', params.timeMax);
const response = await ctx.authorizedFetch(url.toString());
if (!response.ok) {
const error = await response.text();
return {
content: [{ type: 'text', text: `Error: ${error}` }],
isError: true,
};
}
const data = await response.json();
return {
content: [{ type: 'text', text: JSON.stringify(data.items, null, 2) }],
};
},
});
export const createEvent = defineTool({
name: 'google_calendar_create_event',
description: 'Create a new event on Google Calendar with title, time, and optional attendees.',
category: 'calendar',
tags: ['google', 'calendar', 'events', 'create', 'schedule', 'meetings', 'write'],
alwaysVisible: false, // Show when user asks to create/schedule
parameters: z.object({
calendarId: z.string().default('primary'),
summary: z.string().describe('Event title'),
description: z.string().optional().describe('Event description'),
start: z.string().describe('Start time (ISO 8601)'),
end: z.string().describe('End time (ISO 8601)'),
attendees: z.array(z.string()).optional().describe('Email addresses of attendees'),
}),
handler: async (params, ctx) => {
const response = await ctx.authorizedFetch(
`https://www.googleapis.com/calendar/v3/calendars/${params.calendarId}/events`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
summary: params.summary,
description: params.description,
start: { dateTime: params.start },
end: { dateTime: params.end },
attendees: params.attendees?.map(email => ({ email })),
}),
}
);
if (!response.ok) {
const error = await response.text();
return {
content: [{ type: 'text', text: `Error: ${error}` }],
isError: true,
};
}
const event = await response.json();
return {
content: [{ type: 'text', text: `Created event: ${event.htmlLink}` }],
};
},
});
```
## Integration Exports
```typescript
// src/google/calendar/index.ts
export * from './tools';
export * from './resources';
export * from './prompts';
// Convenience groupings
import { listEvents, createEvent, updateEvent, deleteEvent } from './tools';
import { calendarInfo } from './resources';
import { scheduleAssistant } from './prompts';
export const calendarTools = {
all: [listEvents, createEvent, updateEvent, deleteEvent],
alwaysVisible: [listEvents],
readOnly: [listEvents],
fullAccess: [listEvents, createEvent, updateEvent, deleteEvent],
};
export const calendarResources = [calendarInfo];
export const calendarPrompts = [scheduleAssistant];
export const calendarMeta = {
name: 'google-calendar',
category: 'calendar',
scopes: ['https://www.googleapis.com/auth/calendar'],
provider: 'google',
};
```
## Server Usage
```typescript
// In MCP server src/index.ts
import {
calendarTools,
calendarResources,
calendarPrompts,
calendarMeta
} from '@jezweb/mcp-integrations/google/calendar';
import {
gmailTools,
} from '@jezweb/mcp-integrations/google/gmail';
class MyMCP extends McpAgent {
async init() {
// Register Calendar (full access)
calendarTools.all.forEach(tool => {
this.server.tool(
tool.name,
tool.description,
tool.parameters,
(params) => tool.handler(params, this.getToolContext())
);
});
// Register Gmail (read-only)
gmailTools.readOnly.forEach(tool => {
this.server.tool(tool.name, tool.description, tool.parameters,
(params) => tool.handler(params, this.getToolContext())
);
});
// Register resources
calendarResources.forEach(resource => {
this.server.resource(resource.name, resource.uri, resource.metadata,
(uri) => resource.handler(uri, this.getToolContext())
);
});
}
getToolContext(): ToolContext {
return {
authorizedFetch: this.authorizedFetch.bind(this),
userId: this.props?.id,
};
}
}
```
## Tool Search Integration
The MCP server template provides Tool Search support:
```typescript
// Template handles this - not in the package
class MyMCP extends McpAgent {
private toolRegistry: Map<string, ToolDefinition> = new Map();
registerTool(tool: ToolDefinition) {
this.toolRegistry.set(tool.name, tool);
// Only register with MCP if alwaysVisible
if (tool.alwaysVisible) {
this.server.tool(tool.name, tool.description, tool.parameters,
(params) => tool.handler(params, this.getToolContext())
);
}
}
async init() {
// Built-in search tool (always visible)
this.server.tool(
'search_tools',
'Search for available tools by describing what you want to do',
{ query: z.string().describe('What you want to accomplish') },
async ({ query }) => {
const matches = this.searchTools(query);
// Dynamically register matched tools
matches.forEach(tool => {
if (!tool.alwaysVisible) {
this.server.tool(tool.name, tool.description, tool.parameters,
(params) => tool.handler(params, this.getToolContext())
);
}
});
return {
content: [{
type: 'text',
text: `Found ${matches.length} tools:\n${matches.map(t => `- ${t.name}: ${t.description}`).join('\n')}`
}]
};
}
);
// Register all tools to registry
calendarTools.all.forEach(t => this.registerTool(t));
gmailTools.all.forEach(t => this.registerTool(t));
}
searchTools(query: string): ToolDefinition[] {
const queryLower = query.toLowerCase();
const results: Array<{ tool: ToolDefinition; score: number }> = [];
for (const tool of this.toolRegistry.values()) {
let score = 0;
// Match against name, description, category, tags
if (tool.name.toLowerCase().includes(queryLower)) score += 10;
if (tool.description.toLowerCase().includes(queryLower)) score += 5;
if (tool.category.toLowerCase().includes(queryLower)) score += 3;
if (tool.tags.some(t => t.toLowerCase().includes(queryLower))) score += 2;
if (score > 0) {
results.push({ tool, score });
}
}
return results
.sort((a, b) => b.score - a.score)
.slice(0, 10)
.map(r => r.tool);
}
}
```
## Required OAuth Scopes
Each integration declares its required scopes:
| Integration | Scopes |
|-------------|--------|
| Google Calendar | `https://www.googleapis.com/auth/calendar` |
| Gmail | `https://www.googleapis.com/auth/gmail.readonly`, `https://www.googleapis.com/auth/gmail.send` |
| Google Sheets | `https://www.googleapis.com/auth/spreadsheets` |
| Google Drive | `https://www.googleapis.com/auth/drive.readonly` |
| Xero | `openid profile email accounting.transactions accounting.contacts` |
Servers must request appropriate scopes in their OAuth configuration.
## Implementation Phases
### Phase 1: Package Setup ✅
- [x] Create `@jezweb/mcp-integrations` package
- [x] Set up TypeScript, build config
- [x] Define core types and helpers (defineTool, defineResource, definePrompt)
- [ ] Publish to npm (pending first use)
### Phase 2: Google Calendar ✅
- [x] Implement all calendar tools (listEvents, createEvent, updateEvent, deleteEvent, quickAddEvent)
- [x] Add resources (calendarList)
- [x] Add prompts (scheduleAssistant, dailyAgenda)
- [x] Export presets (all, readOnly, fullAccess, alwaysVisible)
- [ ] Test with live Google API (next step)
### Phase 3: Gmail ⏳
- [ ] Implement message tools (list, get, send)
- [ ] Implement thread tools
- [ ] Implement label tools
- [ ] Test and document
### Phase 4: Template Updates ✅
- [x] Add Tool Search to MCP template (searchTools, getAlwaysVisibleTools, searchToolsTool)
- [x] Add alwaysVisible field to ToolMetadata
- [x] Create integrations-example.ts showing package usage
- [ ] Update CUSTOMIZATION.md with package usage guide
### Phase 5: Additional Integrations (ongoing)
- [ ] Google Sheets
- [ ] Google Drive
- [ ] Xero
- [ ] Others as needed
## Benefits
| Benefit | How |
|---------|-----|
| Bug fixes propagate | `npm update @jezweb/mcp-integrations` |
| Consistent patterns | All tools follow same structure |
| No copy-paste errors | Import tested code |
| Tool Search ready | Metadata built into definitions |
| Selective imports | Pick only what you need |
| Easy to extend | Add new integrations to package |
## Repository
Create new repo: `github.com/jezweb/mcp-integrations`
Separate from template to allow independent versioning.