Add more mcp tool metadara + ChatGPT domain verification route (#197)
This commit is contained in:
parent
0ce787889a
commit
7a0ab0de2c
@ -25,6 +25,7 @@ import { Route as AppSupportRouteImport } from './routes/_app/support'
|
||||
import { Route as AppSettingsRouteImport } from './routes/_app/settings'
|
||||
import { Route as AppBillingRouteImport } from './routes/_app/billing'
|
||||
import { Route as AppAiRouteImport } from './routes/_app/ai'
|
||||
import { Route as Char91DotwellKnownChar93OpenaiAppsChallengeRouteImport } from './routes/[.well-known]/openai-apps-challenge'
|
||||
import { Route as ApiAutumnSplatRouteImport } from './routes/api/autumn/$'
|
||||
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
||||
import { Route as AppHelpDataforseoApiKeyRouteImport } from './routes/_app/help/dataforseo-api-key'
|
||||
@ -120,6 +121,12 @@ const AppAiRoute = AppAiRouteImport.update({
|
||||
path: '/ai',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
const Char91DotwellKnownChar93OpenaiAppsChallengeRoute =
|
||||
Char91DotwellKnownChar93OpenaiAppsChallengeRouteImport.update({
|
||||
id: '/.well-known/openai-apps-challenge',
|
||||
path: '/.well-known/openai-apps-challenge',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ApiAutumnSplatRoute = ApiAutumnSplatRouteImport.update({
|
||||
id: '/api/autumn/$',
|
||||
path: '/api/autumn/$',
|
||||
@ -220,6 +227,7 @@ export interface FileRoutesByFullPath {
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/reset-password': typeof ResetPasswordRoute
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/openai-apps-challenge': typeof Char91DotwellKnownChar93OpenaiAppsChallengeRoute
|
||||
'/ai': typeof AppAiRoute
|
||||
'/billing': typeof AppBillingRoute
|
||||
'/settings': typeof AppSettingsRoute
|
||||
@ -251,6 +259,7 @@ export interface FileRoutesByTo {
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/reset-password': typeof ResetPasswordRoute
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/openai-apps-challenge': typeof Char91DotwellKnownChar93OpenaiAppsChallengeRoute
|
||||
'/ai': typeof AppAiRoute
|
||||
'/billing': typeof AppBillingRoute
|
||||
'/settings': typeof AppSettingsRoute
|
||||
@ -283,6 +292,7 @@ export interface FileRoutesById {
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/reset-password': typeof ResetPasswordRoute
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/openai-apps-challenge': typeof Char91DotwellKnownChar93OpenaiAppsChallengeRoute
|
||||
'/_app/ai': typeof AppAiRoute
|
||||
'/_app/billing': typeof AppBillingRoute
|
||||
'/_app/settings': typeof AppSettingsRoute
|
||||
@ -317,6 +327,7 @@ export interface FileRouteTypes {
|
||||
| '/forgot-password'
|
||||
| '/reset-password'
|
||||
| '/verify-email'
|
||||
| '/.well-known/openai-apps-challenge'
|
||||
| '/ai'
|
||||
| '/billing'
|
||||
| '/settings'
|
||||
@ -348,6 +359,7 @@ export interface FileRouteTypes {
|
||||
| '/forgot-password'
|
||||
| '/reset-password'
|
||||
| '/verify-email'
|
||||
| '/.well-known/openai-apps-challenge'
|
||||
| '/ai'
|
||||
| '/billing'
|
||||
| '/settings'
|
||||
@ -379,6 +391,7 @@ export interface FileRouteTypes {
|
||||
| '/forgot-password'
|
||||
| '/reset-password'
|
||||
| '/verify-email'
|
||||
| '/.well-known/openai-apps-challenge'
|
||||
| '/_app/ai'
|
||||
| '/_app/billing'
|
||||
| '/_app/settings'
|
||||
@ -415,6 +428,7 @@ export interface RootRouteChildren {
|
||||
ForgotPasswordRoute: typeof ForgotPasswordRoute
|
||||
ResetPasswordRoute: typeof ResetPasswordRoute
|
||||
VerifyEmailRoute: typeof VerifyEmailRoute
|
||||
Char91DotwellKnownChar93OpenaiAppsChallengeRoute: typeof Char91DotwellKnownChar93OpenaiAppsChallengeRoute
|
||||
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
||||
ApiAutumnSplatRoute: typeof ApiAutumnSplatRoute
|
||||
}
|
||||
@ -533,6 +547,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppAiRouteImport
|
||||
parentRoute: typeof AppRouteRoute
|
||||
}
|
||||
'/.well-known/openai-apps-challenge': {
|
||||
id: '/.well-known/openai-apps-challenge'
|
||||
path: '/.well-known/openai-apps-challenge'
|
||||
fullPath: '/.well-known/openai-apps-challenge'
|
||||
preLoaderRoute: typeof Char91DotwellKnownChar93OpenaiAppsChallengeRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/api/autumn/$': {
|
||||
id: '/api/autumn/$'
|
||||
path: '/api/autumn/$'
|
||||
@ -789,6 +810,8 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
ForgotPasswordRoute: ForgotPasswordRoute,
|
||||
ResetPasswordRoute: ResetPasswordRoute,
|
||||
VerifyEmailRoute: VerifyEmailRoute,
|
||||
Char91DotwellKnownChar93OpenaiAppsChallengeRoute:
|
||||
Char91DotwellKnownChar93OpenaiAppsChallengeRoute,
|
||||
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
||||
ApiAutumnSplatRoute: ApiAutumnSplatRoute,
|
||||
}
|
||||
|
||||
19
src/routes/[.well-known]/openai-apps-challenge.ts
Normal file
19
src/routes/[.well-known]/openai-apps-challenge.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
// Public verification token expected at this well-known URL by OpenAI Apps.
|
||||
const OPENAI_APPS_CHALLENGE_TOKEN =
|
||||
"GEqD0QcIISUHCDhQXqm18K9Hm4Fixm8RMbDxz3nUXsw";
|
||||
|
||||
export const Route = createFileRoute("/.well-known/openai-apps-challenge")({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: async () => {
|
||||
return new Response(OPENAI_APPS_CHALLENGE_TOKEN, {
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
18
src/server/mcp/output-schemas.ts
Normal file
18
src/server/mcp/output-schemas.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const mcpMetaOutputSchema = z
|
||||
.object({
|
||||
url: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
projectId: z.string().optional(),
|
||||
runId: z.string().optional(),
|
||||
creditsCharged: z.number().optional(),
|
||||
creditsRemaining: z.number().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const looseObjectOutputSchema = z.record(z.string(), z.unknown());
|
||||
|
||||
export const optionalMetaOutputSchema = {
|
||||
meta: mcpMetaOutputSchema.optional(),
|
||||
} as const;
|
||||
@ -2,6 +2,10 @@ import { z } from "zod";
|
||||
import { BacklinksService } from "@/server/features/backlinks/services/BacklinksService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import {
|
||||
looseObjectOutputSchema,
|
||||
optionalMetaOutputSchema,
|
||||
} from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import { projectIdSchema } from "@/server/mcp/schemas";
|
||||
|
||||
@ -38,6 +42,16 @@ export const getBacklinksOverviewTool = {
|
||||
description:
|
||||
"Returns a backlinks profile summary (total backlinks, referring domains, top referring domains). Charges credits (~200-500 typical). Requires that the user's DataForSEO account has Backlinks enabled.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
overview: looseObjectOutputSchema,
|
||||
referringDomains: looseObjectOutputSchema,
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
const lookup = { target: args.target, scope: args.scope };
|
||||
|
||||
@ -2,6 +2,10 @@ import { z } from "zod";
|
||||
import { DomainService } from "@/server/features/domain/services/DomainService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import {
|
||||
looseObjectOutputSchema,
|
||||
optionalMetaOutputSchema,
|
||||
} from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
@ -30,6 +34,15 @@ export const getDomainKeywordSuggestionsTool = {
|
||||
description:
|
||||
"Returns the organic keywords a domain ranks for, including position and available metrics. Use after get_domain_overview when you want the detailed keyword opportunity list for a competitor or reference domain. Charges credits (~100-300 typical). Cached for 12 hours.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
keywords: z.array(looseObjectOutputSchema),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
const keywords = await DomainService.getSuggestedKeywords(
|
||||
|
||||
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
import { DomainService } from "@/server/features/domain/services/DomainService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
@ -28,6 +29,21 @@ export const getDomainOverviewTool = {
|
||||
description:
|
||||
"Returns a high-level view of a domain's organic footprint: estimated organic traffic, organic keyword count, backlinks, and referring domains. Use this first for domain research; for the detailed ranked-keyword list, call get_domain_keyword_suggestions next. Charges credits (~100-300 typical). Cached for 12 hours per domain.",
|
||||
inputSchema,
|
||||
outputSchema: z
|
||||
.object({
|
||||
domain: z.string().optional(),
|
||||
organicTraffic: z.number().nullable().optional(),
|
||||
organicKeywords: z.number().nullable().optional(),
|
||||
backlinks: z.number().nullable().optional(),
|
||||
referringDomains: z.number().nullable().optional(),
|
||||
...optionalMetaOutputSchema,
|
||||
})
|
||||
.passthrough(),
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
const result = await DomainService.getOverview(
|
||||
|
||||
@ -3,6 +3,10 @@ import { RankTrackingRepository } from "@/server/features/rank-tracking/reposito
|
||||
import { getLatestResults } from "@/server/features/rank-tracking/services/rankTrackingResults";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import {
|
||||
looseObjectOutputSchema,
|
||||
optionalMetaOutputSchema,
|
||||
} from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import { projectIdSchema } from "@/server/mcp/schemas";
|
||||
|
||||
@ -25,6 +29,19 @@ export const getRankTrackerTool = {
|
||||
description:
|
||||
"Read-only access to rank tracker configs and their latest results. With `trackerId`, returns config + latest snapshot per keyword. Without it, lists all trackers in the project. Free — reads from OpenSEO state, no DataForSEO call. To trigger a new check, use the dashboard.",
|
||||
inputSchema,
|
||||
outputSchema: z
|
||||
.object({
|
||||
configs: z.array(looseObjectOutputSchema).optional(),
|
||||
config: looseObjectOutputSchema.optional(),
|
||||
results: looseObjectOutputSchema.optional(),
|
||||
...optionalMetaOutputSchema,
|
||||
})
|
||||
.passthrough(),
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
if (!args.trackerId) {
|
||||
|
||||
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
import { createDataforseoClient } from "@/server/lib/dataforseoClient";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
@ -37,6 +38,43 @@ export const getSerpResultsTool = {
|
||||
description:
|
||||
"Fetch live Google organic search results for 1-10 keywords. Use this to inspect who ranks for a query, verify competitors, compare SERPs across keywords, or gather source URLs before content planning. Charges credits per keyword (~30-60 each). Does not save results to OpenSEO. Per-keyword errors don't fail the batch.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
results: z.array(
|
||||
z.union([
|
||||
z
|
||||
.object({
|
||||
keyword: z.string(),
|
||||
ok: z.literal(true),
|
||||
items: z.array(
|
||||
z
|
||||
.object({
|
||||
type: z.string().nullable().optional(),
|
||||
rank: z.number().nullable(),
|
||||
title: z.string().nullable(),
|
||||
url: z.string().nullable(),
|
||||
domain: z.string().nullable(),
|
||||
description: z.string().nullable(),
|
||||
})
|
||||
.passthrough(),
|
||||
),
|
||||
})
|
||||
.passthrough(),
|
||||
z
|
||||
.object({
|
||||
keyword: z.string(),
|
||||
ok: z.literal(false),
|
||||
error: z.string(),
|
||||
})
|
||||
.passthrough(),
|
||||
]),
|
||||
),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
const client = createDataforseoClient(context.billing);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { ProjectService } from "@/server/features/projects/services/ProjectService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { getAuth, getBaseUrl, type ToolExtra } from "@/server/mcp/context";
|
||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||
import { buildDashboardUrl } from "@/server/mcp/urls";
|
||||
import { z } from "zod";
|
||||
|
||||
export const listProjectsTool = {
|
||||
name: "list_projects",
|
||||
@ -10,6 +12,24 @@ export const listProjectsTool = {
|
||||
description:
|
||||
"Lists all projects in the user's organization. Free — does not call DataForSEO. Use this whenever you need a `projectId` for another OpenSEO tool. Returns an array of {id, name, domain}; pass the `id` value as `projectId`.",
|
||||
inputSchema: {} as Record<string, never>,
|
||||
outputSchema: {
|
||||
projects: z.array(
|
||||
z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
domain: z.string().nullable().optional(),
|
||||
url: z.string(),
|
||||
})
|
||||
.passthrough(),
|
||||
),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: async (_args: Record<string, never>, extra: ToolExtra) => {
|
||||
const auth = getAuth(extra);
|
||||
|
||||
@ -2,6 +2,10 @@ import { z } from "zod";
|
||||
import { KeywordResearchService } from "@/server/features/keywords/services/KeywordResearchService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import {
|
||||
looseObjectOutputSchema,
|
||||
optionalMetaOutputSchema,
|
||||
} from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import { projectIdSchema } from "@/server/mcp/schemas";
|
||||
|
||||
@ -31,6 +35,17 @@ export const listSavedKeywordsTool = {
|
||||
description:
|
||||
"Lists keywords saved to a project (with cached metrics like search volume, difficulty, CPC, and tags if available). Free — reads from OpenSEO's database, no DataForSEO call. Use tag filters when the user asks for a saved segment; multiple tags match ANY tag.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
rows: z.array(looseObjectOutputSchema),
|
||||
totalCount: z.number(),
|
||||
tags: z.array(looseObjectOutputSchema),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(
|
||||
async (args: z.infer<z.ZodObject<typeof inputSchema>>, context) => {
|
||||
|
||||
@ -2,6 +2,10 @@ import { z } from "zod";
|
||||
import { KeywordResearchService } from "@/server/features/keywords/services/KeywordResearchService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import {
|
||||
looseObjectOutputSchema,
|
||||
optionalMetaOutputSchema,
|
||||
} from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
@ -41,6 +45,35 @@ export const researchKeywordsTool = {
|
||||
description:
|
||||
"Research keyword data (search volume, difficulty, CPC, related ideas) for 1-5 seed keywords in one call. Charges credits per seed (~50-200 credits each, varies by source). Returns per-seed results — a single bad seed won't fail the batch.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
results: z.array(
|
||||
z.union([
|
||||
z
|
||||
.object({
|
||||
seed: z.string(),
|
||||
ok: z.literal(true),
|
||||
rowCount: z.number(),
|
||||
source: z.string(),
|
||||
usedFallback: z.boolean(),
|
||||
topRows: z.array(looseObjectOutputSchema),
|
||||
})
|
||||
.passthrough(),
|
||||
z
|
||||
.object({
|
||||
seed: z.string(),
|
||||
ok: z.literal(false),
|
||||
error: z.string(),
|
||||
})
|
||||
.passthrough(),
|
||||
]),
|
||||
),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
const results = await Promise.all(
|
||||
|
||||
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
import { KeywordResearchService } from "@/server/features/keywords/services/KeywordResearchService";
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { buildProjectMeta } from "@/server/mcp/context";
|
||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||
import { withMcpProjectAuth } from "@/server/mcp/project-auth";
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
@ -44,6 +45,21 @@ export const saveKeywordsTool = {
|
||||
description:
|
||||
"Save keywords to a project's saved-keywords list. Free — does not call DataForSEO. Idempotent: re-saving an existing keyword is a no-op. If tags are provided, missing tags may be created. By default tags are appended; set tagMode=replace to remove existing tags from these saved keywords before applying the provided tags, which is useful for reorganizing keywords into page/topic clusters. Ask the user for confirmation before applying or replacing tags broadly.",
|
||||
inputSchema,
|
||||
outputSchema: {
|
||||
projectId: z.string(),
|
||||
savedCount: z.number(),
|
||||
keywords: z.array(z.string()),
|
||||
tags: z.array(z.string()),
|
||||
tagMode: z.enum(["append", "replace"]),
|
||||
locationCode: z.number(),
|
||||
languageCode: z.string(),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: withMcpProjectAuth(async (args: Args, context) => {
|
||||
if (args.tagMode === "replace" && (args.tags?.length ?? 0) === 0) {
|
||||
|
||||
@ -6,6 +6,8 @@ import {
|
||||
import { mcpResponse } from "@/server/mcp/formatters";
|
||||
import { getAuth, type ToolExtra } from "@/server/mcp/context";
|
||||
import { isHostedServerAuthMode } from "@/server/lib/runtime-env";
|
||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||
import { z } from "zod";
|
||||
|
||||
async function checkBalance(featureId: string, customerId: string) {
|
||||
try {
|
||||
@ -23,6 +25,20 @@ export const whoamiTool = {
|
||||
description:
|
||||
"Returns the authenticated user, organization, server mode, token scopes, and current credit balance. Free — does not call DataForSEO. Use this first to confirm connection context before choosing a project or running paid tools.",
|
||||
inputSchema: {} as Record<string, never>,
|
||||
outputSchema: {
|
||||
userId: z.string(),
|
||||
userEmail: z.string(),
|
||||
organizationId: z.string(),
|
||||
scopes: z.array(z.string()),
|
||||
mode: z.enum(["hosted", "self-hosted"]),
|
||||
creditsRemaining: z.number().nullable(),
|
||||
...optionalMetaOutputSchema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
},
|
||||
},
|
||||
handler: async (_args: Record<string, never>, extra: ToolExtra) => {
|
||||
const auth = getAuth(extra);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user