Skip to content

Commit bbaf349

Browse files
committed
feat: adds posthog and railway integrations
1 parent 7fa26aa commit bbaf349

15 files changed

Lines changed: 976 additions & 15 deletions

index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { intercomIntegration } from './src/integrations/intercom.js';
6161
import { hubspotIntegration } from './src/integrations/hubspot.js';
6262
import { youtubeIntegration } from './src/integrations/youtube.js';
6363
import { cursorIntegration } from './src/integrations/cursor.js';
64+
import { posthogIntegration } from './src/integrations/posthog.js';
6465

6566
/**
6667
* Default MCP Client with all integrations pre-configured
@@ -128,9 +129,9 @@ export const client = createMCPClient({
128129
hubspotIntegration(),
129130
youtubeIntegration(),
130131
cursorIntegration(),
132+
posthogIntegration(),
131133
],
132134
// Fetch configured integrations from server since default client has all integrations
133135
// but only some may be configured on the server with OAuth credentials
134136
useServerConfig: true,
135137
});
136-

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "integrate-sdk",
3-
"version": "0.9.28",
3+
"version": "0.9.29",
44
"description": "Type-safe 3rd party integration SDK for the Integrate MCP server",
55
"type": "module",
66
"main": "./dist/index.js",

src/adapters/base-handler.ts

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,35 @@ import { createLogger, type LogContext } from '../utils/logger.js';
1616
const SERVER_LOG_CONTEXT: LogContext = 'server';
1717
const logger = createLogger('OAuthHandler', SERVER_LOG_CONTEXT);
1818

19+
const OAUTH_CONFIG_FIELDS = new Set([
20+
'clientId', 'clientSecret', 'scopes', 'optionalScopes', 'redirectUri',
21+
'client_id', 'client_secret', 'scope', 'optional_scope', 'redirect_uri',
22+
'provider',
23+
]);
24+
25+
function getForwardableProviderConfig(config?: Record<string, any>): Record<string, string> {
26+
if (!config) {
27+
return {};
28+
}
29+
30+
return Object.fromEntries(
31+
Object.entries(config)
32+
.filter(([key, value]) => value !== undefined && value !== null && !OAUTH_CONFIG_FIELDS.has(key))
33+
.map(([key, value]) => [key, String(value)])
34+
);
35+
}
36+
37+
function getStoredProviderConfig(config?: Record<string, any>): Record<string, unknown> | undefined {
38+
const baseUrl = config?.baseUrl || config?.apiBaseUrl;
39+
if (!baseUrl) {
40+
return undefined;
41+
}
42+
43+
return {
44+
baseUrl: String(baseUrl),
45+
};
46+
}
47+
1948
/**
2049
* MCP Server URL - managed by Integrate
2150
*/
@@ -370,18 +399,9 @@ export class OAuthHandler {
370399
// Add provider-specific config parameters (e.g., Notion's 'owner' parameter)
371400
// Fields already handled explicitly above — skip them if they accidentally
372401
// appear in the provider-specific config (e.g., from a ...config spread)
373-
const OAUTH_FIELDS = new Set([
374-
'clientId', 'clientSecret', 'scopes', 'optionalScopes', 'redirectUri',
375-
'client_id', 'client_secret', 'scope', 'optional_scope', 'redirect_uri',
376-
'provider',
377-
]);
378-
379-
if (providerConfig.config) {
380-
for (const [key, value] of Object.entries(providerConfig.config)) {
381-
if (value !== undefined && value !== null && !OAUTH_FIELDS.has(key)) {
382-
url.searchParams.set(key, String(value));
383-
}
384-
}
402+
const extraConfig = getForwardableProviderConfig(providerConfig.config);
403+
for (const [key, value] of Object.entries(extraConfig)) {
404+
url.searchParams.set(key, value);
385405
}
386406

387407
// Forward to MCP server
@@ -530,6 +550,7 @@ export class OAuthHandler {
530550
client_id: providerConfig.clientId,
531551
client_secret: providerConfig.clientSecret,
532552
redirect_uri: providerConfig.redirectUri,
553+
...getForwardableProviderConfig(providerConfig.config),
533554
}),
534555
});
535556

@@ -559,6 +580,7 @@ export class OAuthHandler {
559580
scopes: result.scopes
560581
? result.scopes.flatMap((s: string) => s.split(' ').filter(Boolean))
561582
: result.scopes,
583+
providerConfig: getStoredProviderConfig(providerConfig.config),
562584
};
563585

564586
// Prefer email returned directly from the callback response (e.g. Google id_token),
@@ -755,6 +777,13 @@ export class OAuthHandler {
755777
body.subdomain = providerConfig.config.subdomain;
756778
}
757779

780+
const extraConfig = getForwardableProviderConfig(providerConfig.config);
781+
for (const [key, value] of Object.entries(extraConfig)) {
782+
if (body[key] === undefined) {
783+
body[key] = value;
784+
}
785+
}
786+
758787
// Forward to MCP server for token refresh
759788
const url = new URL('/oauth/refresh', this.serverUrl);
760789

@@ -790,6 +819,7 @@ export class OAuthHandler {
790819
scopes: result.scopes
791820
? result.scopes.flatMap((s: string) => s.split(' ').filter(Boolean))
792821
: result.scopes,
822+
providerConfig: getStoredProviderConfig(providerConfig.config),
793823
};
794824

795825
const email = result.email || await fetchUserEmail(refreshRequest.provider, tokenData);

src/client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type { GmailIntegrationClient } from "./integrations/gmail-client.js";
3333
import type { NotionIntegrationClient } from "./integrations/notion-client.js";
3434
import type { SlackIntegrationClient } from "./integrations/slack-client.js";
3535
import type { LinearIntegrationClient } from "./integrations/linear-client.js";
36+
import type { RailwayIntegrationClient } from "./integrations/railway-client.js";
3637
import type { VercelIntegrationClient } from "./integrations/vercel-client.js";
3738
import type { ZendeskIntegrationClient } from "./integrations/zendesk-client.js";
3839
import type { StripeIntegrationClient } from "./integrations/stripe-client.js";
@@ -53,6 +54,7 @@ import type { IntercomIntegrationClient } from "./integrations/intercom-client.j
5354
import type { HubSpotIntegrationClient } from "./integrations/hubspot-client.js";
5455
import type { YouTubeIntegrationClient } from "./integrations/youtube-client.js";
5556
import type { CursorIntegrationClient } from "./integrations/cursor-client.js";
57+
import type { PostHogIntegrationClient } from "./integrations/posthog-client.js";
5658
import type { ServerIntegrationClient } from "./integrations/server-client.js";
5759
import { TriggerClient } from "./triggers/client.js";
5860
import { OAuthManager } from "./oauth/manager.js";
@@ -196,6 +198,8 @@ type IntegrationNamespaces<TIntegrations extends readonly MCPIntegration[]> = {
196198
? "slack"
197199
: K extends "linear"
198200
? "linear"
201+
: K extends "railway"
202+
? "railway"
199203
: K extends "vercel"
200204
? "vercel"
201205
: K extends "zendesk"
@@ -236,12 +240,15 @@ type IntegrationNamespaces<TIntegrations extends readonly MCPIntegration[]> = {
236240
? "youtube"
237241
: K extends "cursor"
238242
? "cursor"
243+
: K extends "posthog"
244+
? "posthog"
239245
: never]:
240246
K extends "github" ? GitHubIntegrationClient :
241247
K extends "gmail" ? GmailIntegrationClient :
242248
K extends "notion" ? NotionIntegrationClient :
243249
K extends "slack" ? SlackIntegrationClient :
244250
K extends "linear" ? LinearIntegrationClient :
251+
K extends "railway" ? RailwayIntegrationClient :
245252
K extends "vercel" ? VercelIntegrationClient :
246253
K extends "zendesk" ? ZendeskIntegrationClient :
247254
K extends "stripe" ? StripeIntegrationClient :
@@ -262,6 +269,7 @@ type IntegrationNamespaces<TIntegrations extends readonly MCPIntegration[]> = {
262269
K extends "hubspot" ? HubSpotIntegrationClient :
263270
K extends "youtube" ? YouTubeIntegrationClient :
264271
K extends "cursor" ? CursorIntegrationClient :
272+
K extends "posthog" ? PostHogIntegrationClient :
265273
never;
266274
};
267275

@@ -495,6 +503,9 @@ export class MCPClientBase<TIntegrations extends readonly MCPIntegration[] = rea
495503
if (integrationIds.includes("linear")) {
496504
(this as any).linear = this.createIntegrationProxy("linear");
497505
}
506+
if (integrationIds.includes("railway")) {
507+
(this as any).railway = this.createIntegrationProxy("railway");
508+
}
498509
if (integrationIds.includes("vercel")) {
499510
(this as any).vercel = this.createIntegrationProxy("vercel");
500511
}
@@ -525,6 +536,9 @@ export class MCPClientBase<TIntegrations extends readonly MCPIntegration[] = rea
525536
if (integrationIds.includes("gslides")) {
526537
(this as any).gslides = this.createIntegrationProxy("gslides");
527538
}
539+
if (integrationIds.includes("posthog")) {
540+
(this as any).posthog = this.createIntegrationProxy("posthog");
541+
}
528542

529543
// Server namespace is always available
530544
this.server = this.createServerProxy() as any;

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export type { SlackIntegrationConfig, SlackTools, SlackIntegrationClient } from
9898
export { linearIntegration } from "./integrations/linear.js";
9999
export type { LinearIntegrationConfig, LinearTools, LinearIntegrationClient } from "./integrations/linear.js";
100100

101+
export { railwayIntegration } from "./integrations/railway.js";
102+
export type { RailwayIntegrationConfig, RailwayTools, RailwayScopes, RailwayIntegrationClient } from "./integrations/railway.js";
103+
101104
export { vercelIntegration } from "./integrations/vercel.js";
102105
export type { VercelIntegrationConfig, VercelTools, VercelIntegrationClient } from "./integrations/vercel.js";
103106

@@ -161,6 +164,9 @@ export type { YouTubeIntegrationConfig, YouTubeTools, YouTubeIntegrationClient }
161164
export { cursorIntegration } from "./integrations/cursor.js";
162165
export type { CursorIntegrationConfig, CursorTools, CursorIntegrationClient } from "./integrations/cursor.js";
163166

167+
export { posthogIntegration } from "./integrations/posthog.js";
168+
export type { PostHogIntegrationConfig, PostHogTools, PostHogScopes, PostHogIntegrationClient } from "./integrations/posthog.js";
169+
164170
export { granolaIntegration } from "./integrations/granola.js";
165171
export type { GranolaIntegrationOptions, GranolaTools } from "./integrations/granola.js";
166172

src/integrations/library-metadata.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
*/
55

66
export type IntegrationCategory =
7+
| "Analytics"
78
| "Business"
89
| "Productivity"
910
| "Communication"
1011
| "Engineering"
12+
| "Infrastructure"
1113
| "Storage"
1214
| "Other";
1315

@@ -18,10 +20,12 @@ type LibraryEntry = {
1820

1921
/** Display order for library section headers (categories not listed sort before "Other"). */
2022
export const INTEGRATION_CATEGORY_ORDER: readonly IntegrationCategory[] = [
23+
"Analytics",
2124
"Productivity",
2225
"Business",
2326
"Communication",
2427
"Engineering",
28+
"Infrastructure",
2529
"Storage",
2630
"Other",
2731
] as const;
@@ -86,10 +90,18 @@ export const INTEGRATION_LIBRARY_METADATA: Record<string, LibraryEntry> = {
8690
description: "Manage Polar products, orders, and subscriptions",
8791
category: "Business",
8892
},
93+
posthog: {
94+
description: "Read PostHog organizations, projects, insights, and feature flags",
95+
category: "Analytics",
96+
},
8997
ramp: {
9098
description: "Manage Ramp corporate cards, bills, and spend",
9199
category: "Business",
92100
},
101+
railway: {
102+
description: "Manage Railway workspaces, projects, services, deployments, variables, domains, and volumes",
103+
category: "Infrastructure",
104+
},
93105
slack: {
94106
description: "Send and manage Slack messages and channels",
95107
category: "Communication",

src/integrations/posthog-client.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* PostHog Integration Client Types
3+
* Fully typed interface for PostHog integration methods
4+
*/
5+
6+
import type { MCPToolCallResponse } from "../protocol/messages.js";
7+
8+
export interface PostHogCurrentUser {
9+
id: string;
10+
email?: string;
11+
first_name?: string;
12+
last_name?: string;
13+
role?: string;
14+
[key: string]: any;
15+
}
16+
17+
export interface PostHogOrganization {
18+
id: string;
19+
name: string;
20+
slug?: string;
21+
membership_level?: string;
22+
[key: string]: any;
23+
}
24+
25+
export interface PostHogProject {
26+
id: number | string;
27+
name: string;
28+
uuid?: string;
29+
created_at?: string;
30+
updated_at?: string;
31+
[key: string]: any;
32+
}
33+
34+
export interface PostHogInsight {
35+
id: number | string;
36+
name?: string;
37+
short_id?: string;
38+
description?: string;
39+
[key: string]: any;
40+
}
41+
42+
export interface PostHogDashboard {
43+
id: number | string;
44+
name: string;
45+
description?: string;
46+
[key: string]: any;
47+
}
48+
49+
export interface PostHogFeatureFlag {
50+
id: number | string;
51+
key: string;
52+
name?: string;
53+
active?: boolean;
54+
[key: string]: any;
55+
}
56+
57+
export interface PostHogExperiment {
58+
id: number | string;
59+
name?: string;
60+
feature_flag_key?: string;
61+
[key: string]: any;
62+
}
63+
64+
export interface PostHogAnnotation {
65+
id: number | string;
66+
content?: string;
67+
date_marker?: string;
68+
[key: string]: any;
69+
}
70+
71+
export interface PostHogCohort {
72+
id: number | string;
73+
name: string;
74+
count?: number;
75+
[key: string]: any;
76+
}
77+
78+
export interface PostHogEventDefinition {
79+
id: number | string;
80+
name: string;
81+
description?: string;
82+
[key: string]: any;
83+
}
84+
85+
export interface PostHogPropertyDefinition {
86+
id: number | string;
87+
name: string;
88+
property_type?: string;
89+
[key: string]: any;
90+
}
91+
92+
export interface PostHogPerson {
93+
id: string;
94+
name?: string;
95+
distinct_ids?: string[];
96+
properties?: Record<string, any>;
97+
[key: string]: any;
98+
}
99+
100+
export interface PostHogSessionRecording {
101+
id: string;
102+
start_time?: string;
103+
end_time?: string;
104+
distinct_id?: string;
105+
[key: string]: any;
106+
}
107+
108+
export interface PostHogIntegrationClient {
109+
getCurrentUser(params?: Record<string, never>): Promise<MCPToolCallResponse>;
110+
listOrganizations(params?: { limit?: number; offset?: number }): Promise<MCPToolCallResponse>;
111+
getOrganization(params: { organization_id: string }): Promise<MCPToolCallResponse>;
112+
listProjects(params: { organization_id: string; limit?: number; offset?: number; search?: string }): Promise<MCPToolCallResponse>;
113+
getProject(params: { organization_id: string; project_id: string | number }): Promise<MCPToolCallResponse>;
114+
runHogqlQuery(params: { project_id: string | number; query: string; name?: string; kind?: string }): Promise<MCPToolCallResponse>;
115+
listInsights(params: { project_id: string | number; limit?: number; offset?: number; search?: string; refresh?: boolean; insight?: string; basic?: boolean }): Promise<MCPToolCallResponse>;
116+
getInsight(params: { project_id: string | number; insight_id: string | number; refresh?: boolean; from_dashboard?: string | number }): Promise<MCPToolCallResponse>;
117+
listDashboards(params: { project_id: string | number; limit?: number; offset?: number; search?: string }): Promise<MCPToolCallResponse>;
118+
getDashboard(params: { project_id: string | number; dashboard_id: string | number }): Promise<MCPToolCallResponse>;
119+
listFeatureFlags(params: { project_id: string | number; limit?: number; offset?: number; search?: string }): Promise<MCPToolCallResponse>;
120+
getFeatureFlag(params: { project_id: string | number; flag_id: string | number }): Promise<MCPToolCallResponse>;
121+
listExperiments(params: { project_id: string | number; limit?: number; offset?: number }): Promise<MCPToolCallResponse>;
122+
getExperiment(params: { project_id: string | number; experiment_id: string | number }): Promise<MCPToolCallResponse>;
123+
listAnnotations(params: { project_id: string | number; limit?: number; offset?: number }): Promise<MCPToolCallResponse>;
124+
getAnnotation(params: { project_id: string | number; annotation_id: string | number }): Promise<MCPToolCallResponse>;
125+
listCohorts(params: { project_id: string | number; limit?: number; offset?: number }): Promise<MCPToolCallResponse>;
126+
getCohort(params: { project_id: string | number; cohort_id: string | number }): Promise<MCPToolCallResponse>;
127+
listEventDefinitions(params: { project_id: string | number; limit?: number; offset?: number; search?: string }): Promise<MCPToolCallResponse>;
128+
getEventDefinition(params: { project_id: string | number; event_definition_id: string | number }): Promise<MCPToolCallResponse>;
129+
listPropertyDefinitions(params: { project_id: string | number; limit?: number; offset?: number; search?: string }): Promise<MCPToolCallResponse>;
130+
getPropertyDefinition(params: { project_id: string | number; property_definition_id: string | number }): Promise<MCPToolCallResponse>;
131+
listPersons(params: { project_id: string | number; limit?: number; offset?: number; search?: string; email?: string; distinct_id?: string }): Promise<MCPToolCallResponse>;
132+
getPerson(params: { project_id: string | number; person_id: string }): Promise<MCPToolCallResponse>;
133+
listSessionRecordings(params: { project_id: string | number; limit?: number; offset?: number }): Promise<MCPToolCallResponse>;
134+
getSessionRecording(params: { project_id: string | number; recording_id: string }): Promise<MCPToolCallResponse>;
135+
}

0 commit comments

Comments
 (0)