api-mapper-client 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_mapper_client-1.0.0/.gitignore +397 -0
- api_mapper_client-1.0.0/LICENSE +21 -0
- api_mapper_client-1.0.0/PKG-INFO +82 -0
- api_mapper_client-1.0.0/README.md +50 -0
- api_mapper_client-1.0.0/api_mapper_client/__init__.py +17 -0
- api_mapper_client-1.0.0/api_mapper_client/auth/__init__.py +12 -0
- api_mapper_client-1.0.0/api_mapper_client/auth/api_key.py +13 -0
- api_mapper_client-1.0.0/api_mapper_client/auth/base.py +7 -0
- api_mapper_client-1.0.0/api_mapper_client/auth/delegated_bearer.py +18 -0
- api_mapper_client-1.0.0/api_mapper_client/auth/oauth2_client_credentials.py +71 -0
- api_mapper_client-1.0.0/api_mapper_client/client.py +220 -0
- api_mapper_client-1.0.0/api_mapper_client/contracts.py +28 -0
- api_mapper_client-1.0.0/api_mapper_langchain/__init__.py +3 -0
- api_mapper_client-1.0.0/api_mapper_langchain/toolkit.py +53 -0
- api_mapper_client-1.0.0/api_mapper_langgraph/__init__.py +3 -0
- api_mapper_client-1.0.0/api_mapper_langgraph/nodes.py +25 -0
- api_mapper_client-1.0.0/pyproject.toml +61 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
## Ignore Visual Studio temporary files, build results, and
|
|
2
|
+
## files generated by popular Visual Studio add-ons.
|
|
3
|
+
##
|
|
4
|
+
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
5
|
+
|
|
6
|
+
# User-specific files
|
|
7
|
+
*.rsuser
|
|
8
|
+
*.suo
|
|
9
|
+
*.user
|
|
10
|
+
*.userosscache
|
|
11
|
+
*.sln.docstates
|
|
12
|
+
|
|
13
|
+
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
14
|
+
*.userprefs
|
|
15
|
+
|
|
16
|
+
# Mono auto generated files
|
|
17
|
+
mono_crash.*
|
|
18
|
+
|
|
19
|
+
# Build results
|
|
20
|
+
[Dd]ebug/
|
|
21
|
+
[Dd]ebugPublic/
|
|
22
|
+
[Rr]elease/
|
|
23
|
+
[Rr]eleases/
|
|
24
|
+
x64/
|
|
25
|
+
x86/
|
|
26
|
+
[Ww][Ii][Nn]32/
|
|
27
|
+
[Aa][Rr][Mm]/
|
|
28
|
+
[Aa][Rr][Mm]64/
|
|
29
|
+
bld/
|
|
30
|
+
[Bb]in/
|
|
31
|
+
[Oo]bj/
|
|
32
|
+
[Ll]og/
|
|
33
|
+
[Ll]ogs/
|
|
34
|
+
|
|
35
|
+
# Visual Studio 2015/2017 cache/options directory
|
|
36
|
+
.vs/
|
|
37
|
+
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
38
|
+
#wwwroot/
|
|
39
|
+
|
|
40
|
+
# Visual Studio 2017 auto generated files
|
|
41
|
+
Generated\ Files/
|
|
42
|
+
|
|
43
|
+
# MSTest test Results
|
|
44
|
+
[Tt]est[Rr]esult*/
|
|
45
|
+
[Bb]uild[Ll]og.*
|
|
46
|
+
|
|
47
|
+
# NUnit
|
|
48
|
+
*.VisualState.xml
|
|
49
|
+
TestResult.xml
|
|
50
|
+
nunit-*.xml
|
|
51
|
+
|
|
52
|
+
# Build Results of an ATL Project
|
|
53
|
+
[Dd]ebugPS/
|
|
54
|
+
[Rr]eleasePS/
|
|
55
|
+
dlldata.c
|
|
56
|
+
|
|
57
|
+
# Benchmark Results
|
|
58
|
+
BenchmarkDotNet.Artifacts/
|
|
59
|
+
|
|
60
|
+
# .NET Core
|
|
61
|
+
project.lock.json
|
|
62
|
+
project.fragment.lock.json
|
|
63
|
+
artifacts/
|
|
64
|
+
|
|
65
|
+
# ASP.NET Scaffolding
|
|
66
|
+
ScaffoldingReadMe.txt
|
|
67
|
+
|
|
68
|
+
# StyleCop
|
|
69
|
+
StyleCopReport.xml
|
|
70
|
+
|
|
71
|
+
# Files built by Visual Studio
|
|
72
|
+
*_i.c
|
|
73
|
+
*_p.c
|
|
74
|
+
*_h.h
|
|
75
|
+
*.ilk
|
|
76
|
+
*.meta
|
|
77
|
+
*.obj
|
|
78
|
+
*.iobj
|
|
79
|
+
*.pch
|
|
80
|
+
*.pdb
|
|
81
|
+
*.ipdb
|
|
82
|
+
*.pgc
|
|
83
|
+
*.pgd
|
|
84
|
+
*.rsp
|
|
85
|
+
*.sbr
|
|
86
|
+
*.tlb
|
|
87
|
+
*.tli
|
|
88
|
+
*.tlh
|
|
89
|
+
*.tmp
|
|
90
|
+
*.tmp_proj
|
|
91
|
+
*_wpftmp.csproj
|
|
92
|
+
*.log
|
|
93
|
+
*.vspscc
|
|
94
|
+
*.vssscc
|
|
95
|
+
.builds
|
|
96
|
+
*.pidb
|
|
97
|
+
*.svclog
|
|
98
|
+
*.scc
|
|
99
|
+
|
|
100
|
+
# Chutzpah Test files
|
|
101
|
+
_Chutzpah*
|
|
102
|
+
|
|
103
|
+
# Visual C++ cache files
|
|
104
|
+
ipch/
|
|
105
|
+
*.aps
|
|
106
|
+
*.ncb
|
|
107
|
+
*.opendb
|
|
108
|
+
*.opensdf
|
|
109
|
+
*.sdf
|
|
110
|
+
*.cachefile
|
|
111
|
+
*.VC.db
|
|
112
|
+
*.VC.VC.opendb
|
|
113
|
+
|
|
114
|
+
# Visual Studio profiler
|
|
115
|
+
*.psess
|
|
116
|
+
*.vsp
|
|
117
|
+
*.vspx
|
|
118
|
+
*.sap
|
|
119
|
+
|
|
120
|
+
# Visual Studio Trace Files
|
|
121
|
+
*.e2e
|
|
122
|
+
|
|
123
|
+
# TFS 2012 Local Workspace
|
|
124
|
+
$tf/
|
|
125
|
+
|
|
126
|
+
# Guidance Automation Toolkit
|
|
127
|
+
*.gpState
|
|
128
|
+
|
|
129
|
+
# ReSharper is a .NET coding add-in
|
|
130
|
+
_ReSharper*/
|
|
131
|
+
*.[Rr]e[Ss]harper
|
|
132
|
+
*.DotSettings.user
|
|
133
|
+
|
|
134
|
+
# TeamCity is a build add-in
|
|
135
|
+
_TeamCity*
|
|
136
|
+
|
|
137
|
+
# DotCover is a Code Coverage Tool
|
|
138
|
+
*.dotCover
|
|
139
|
+
|
|
140
|
+
# AxoCover is a Code Coverage Tool
|
|
141
|
+
.axoCover/*
|
|
142
|
+
!.axoCover/settings.json
|
|
143
|
+
|
|
144
|
+
# Coverlet is a free, cross platform Code Coverage Tool
|
|
145
|
+
coverage*.json
|
|
146
|
+
coverage*.xml
|
|
147
|
+
coverage*.info
|
|
148
|
+
|
|
149
|
+
# Visual Studio code coverage results
|
|
150
|
+
*.coverage
|
|
151
|
+
*.coveragexml
|
|
152
|
+
|
|
153
|
+
# NCrunch
|
|
154
|
+
_NCrunch_*
|
|
155
|
+
.*crunch*.local.xml
|
|
156
|
+
nCrunchTemp_*
|
|
157
|
+
|
|
158
|
+
# MightyMoose
|
|
159
|
+
*.mm.*
|
|
160
|
+
AutoTest.Net/
|
|
161
|
+
|
|
162
|
+
# Web workbench (sass)
|
|
163
|
+
.sass-cache/
|
|
164
|
+
|
|
165
|
+
# Installshield output folder
|
|
166
|
+
[Ee]xpress/
|
|
167
|
+
|
|
168
|
+
# DocProject is a documentation generator add-in
|
|
169
|
+
DocProject/buildhelp/
|
|
170
|
+
DocProject/Help/*.HxT
|
|
171
|
+
DocProject/Help/*.HxC
|
|
172
|
+
DocProject/Help/*.hhc
|
|
173
|
+
DocProject/Help/*.hhk
|
|
174
|
+
DocProject/Help/*.hhp
|
|
175
|
+
DocProject/Help/Html2
|
|
176
|
+
DocProject/Help/html
|
|
177
|
+
|
|
178
|
+
# Click-Once directory
|
|
179
|
+
publish/
|
|
180
|
+
|
|
181
|
+
# Publish Web Output
|
|
182
|
+
*.[Pp]ublish.xml
|
|
183
|
+
*.azurePubxml
|
|
184
|
+
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
185
|
+
# but database connection strings (with potential passwords) will be unencrypted
|
|
186
|
+
*.pubxml
|
|
187
|
+
*.publishproj
|
|
188
|
+
|
|
189
|
+
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
190
|
+
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
191
|
+
# in these scripts will be unencrypted
|
|
192
|
+
PublishScripts/
|
|
193
|
+
|
|
194
|
+
# Local development secrets and environment files
|
|
195
|
+
.env
|
|
196
|
+
.env.*
|
|
197
|
+
!.env.example
|
|
198
|
+
!**/.env.example
|
|
199
|
+
!.env.*.example
|
|
200
|
+
!**/.env.*.example
|
|
201
|
+
set-platform-development-secrets.sh
|
|
202
|
+
|
|
203
|
+
# Node / Next.js
|
|
204
|
+
node_modules/
|
|
205
|
+
.next/
|
|
206
|
+
out/
|
|
207
|
+
npm-debug.log*
|
|
208
|
+
yarn-debug.log*
|
|
209
|
+
yarn-error.log*
|
|
210
|
+
pnpm-debug.log*
|
|
211
|
+
|
|
212
|
+
# Generated public site export
|
|
213
|
+
src/PublicSite/CodedProjects.AI.ApiMapper.Public.Web/wwwroot/
|
|
214
|
+
|
|
215
|
+
# NuGet Packages
|
|
216
|
+
*.nupkg
|
|
217
|
+
# NuGet Symbol Packages
|
|
218
|
+
*.snupkg
|
|
219
|
+
# The packages folder can be ignored because of Package Restore
|
|
220
|
+
**/[Pp]ackages/*
|
|
221
|
+
# except build/, which is used as an MSBuild target.
|
|
222
|
+
!**/[Pp]ackages/build/
|
|
223
|
+
# Uncomment if necessary however generally it will be regenerated when needed
|
|
224
|
+
#!**/[Pp]ackages/repositories.config
|
|
225
|
+
# NuGet v3's project.json files produces more ignorable files
|
|
226
|
+
*.nuget.props
|
|
227
|
+
*.nuget.targets
|
|
228
|
+
|
|
229
|
+
# Microsoft Azure Build Output
|
|
230
|
+
csx/
|
|
231
|
+
*.build.csdef
|
|
232
|
+
|
|
233
|
+
# Microsoft Azure Emulator
|
|
234
|
+
ecf/
|
|
235
|
+
rcf/
|
|
236
|
+
|
|
237
|
+
# Windows Store app package directories and files
|
|
238
|
+
AppPackages/
|
|
239
|
+
BundleArtifacts/
|
|
240
|
+
Package.StoreAssociation.xml
|
|
241
|
+
_pkginfo.txt
|
|
242
|
+
*.appx
|
|
243
|
+
*.appxbundle
|
|
244
|
+
*.appxupload
|
|
245
|
+
|
|
246
|
+
# Visual Studio cache files
|
|
247
|
+
# files ending in .cache can be ignored
|
|
248
|
+
*.[Cc]ache
|
|
249
|
+
# but keep track of directories ending in .cache
|
|
250
|
+
!?*.[Cc]ache/
|
|
251
|
+
|
|
252
|
+
# Others
|
|
253
|
+
ClientBin/
|
|
254
|
+
~$*
|
|
255
|
+
*~
|
|
256
|
+
*.dbmdl
|
|
257
|
+
*.dbproj.schemaview
|
|
258
|
+
*.jfm
|
|
259
|
+
*.pfx
|
|
260
|
+
*.publishsettings
|
|
261
|
+
orleans.codegen.cs
|
|
262
|
+
|
|
263
|
+
# Including strong name files can present a security risk
|
|
264
|
+
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
265
|
+
#*.snk
|
|
266
|
+
|
|
267
|
+
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
268
|
+
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
269
|
+
#bower_components/
|
|
270
|
+
|
|
271
|
+
# RIA/Silverlight projects
|
|
272
|
+
Generated_Code/
|
|
273
|
+
|
|
274
|
+
# Backup & report files from converting an old project file
|
|
275
|
+
# to a newer Visual Studio version. Backup files are not needed,
|
|
276
|
+
# because we have git ;-)
|
|
277
|
+
_UpgradeReport_Files/
|
|
278
|
+
Backup*/
|
|
279
|
+
UpgradeLog*.XML
|
|
280
|
+
UpgradeLog*.htm
|
|
281
|
+
ServiceFabricBackup/
|
|
282
|
+
*.rptproj.bak
|
|
283
|
+
|
|
284
|
+
# SQL Server files
|
|
285
|
+
*.mdf
|
|
286
|
+
*.ldf
|
|
287
|
+
*.ndf
|
|
288
|
+
|
|
289
|
+
# Business Intelligence projects
|
|
290
|
+
*.rdl.data
|
|
291
|
+
*.bim.layout
|
|
292
|
+
*.bim_*.settings
|
|
293
|
+
*.rptproj.rsuser
|
|
294
|
+
*- [Bb]ackup.rdl
|
|
295
|
+
*- [Bb]ackup ([0-9]).rdl
|
|
296
|
+
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
297
|
+
|
|
298
|
+
# Microsoft Fakes
|
|
299
|
+
FakesAssemblies/
|
|
300
|
+
|
|
301
|
+
# GhostDoc plugin setting file
|
|
302
|
+
*.GhostDoc.xml
|
|
303
|
+
|
|
304
|
+
# Node.js Tools for Visual Studio
|
|
305
|
+
.ntvs_analysis.dat
|
|
306
|
+
node_modules/
|
|
307
|
+
|
|
308
|
+
# Visual Studio 6 build log
|
|
309
|
+
*.plg
|
|
310
|
+
|
|
311
|
+
# Visual Studio 6 workspace options file
|
|
312
|
+
*.opt
|
|
313
|
+
|
|
314
|
+
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
315
|
+
*.vbw
|
|
316
|
+
|
|
317
|
+
# Visual Studio LightSwitch build output
|
|
318
|
+
**/*.HTMLClient/GeneratedArtifacts
|
|
319
|
+
**/*.DesktopClient/GeneratedArtifacts
|
|
320
|
+
**/*.DesktopClient/ModelManifest.xml
|
|
321
|
+
**/*.Server/GeneratedArtifacts
|
|
322
|
+
**/*.Server/ModelManifest.xml
|
|
323
|
+
_Pvt_Extensions
|
|
324
|
+
|
|
325
|
+
# Paket dependency manager
|
|
326
|
+
.paket/paket.exe
|
|
327
|
+
paket-files/
|
|
328
|
+
|
|
329
|
+
# FAKE - F# Make
|
|
330
|
+
.fake/
|
|
331
|
+
|
|
332
|
+
# CodeRush personal settings
|
|
333
|
+
.cr/personal
|
|
334
|
+
|
|
335
|
+
# Python Tools for Visual Studio (PTVS)
|
|
336
|
+
__pycache__/
|
|
337
|
+
*.pyc
|
|
338
|
+
|
|
339
|
+
# Cake - Uncomment if you are using it
|
|
340
|
+
# tools/**
|
|
341
|
+
# !tools/packages.config
|
|
342
|
+
|
|
343
|
+
# Tabs Studio
|
|
344
|
+
*.tss
|
|
345
|
+
|
|
346
|
+
# Telerik's JustMock configuration file
|
|
347
|
+
*.jmconfig
|
|
348
|
+
|
|
349
|
+
# BizTalk build output
|
|
350
|
+
*.btp.cs
|
|
351
|
+
*.btm.cs
|
|
352
|
+
*.odx.cs
|
|
353
|
+
*.xsd.cs
|
|
354
|
+
|
|
355
|
+
# OpenCover UI analysis results
|
|
356
|
+
OpenCover/
|
|
357
|
+
|
|
358
|
+
# Azure Stream Analytics local run output
|
|
359
|
+
ASALocalRun/
|
|
360
|
+
|
|
361
|
+
# MSBuild Binary and Structured Log
|
|
362
|
+
*.binlog
|
|
363
|
+
|
|
364
|
+
# NVidia Nsight GPU debugger configuration file
|
|
365
|
+
*.nvuser
|
|
366
|
+
|
|
367
|
+
# MFractors (Xamarin productivity tool) working folder
|
|
368
|
+
.mfractor/
|
|
369
|
+
|
|
370
|
+
# Local History for Visual Studio
|
|
371
|
+
.localhistory/
|
|
372
|
+
|
|
373
|
+
# BeatPulse healthcheck temp database
|
|
374
|
+
healthchecksdb
|
|
375
|
+
|
|
376
|
+
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
377
|
+
MigrationBackup/
|
|
378
|
+
|
|
379
|
+
# Ionide (cross platform F# VS Code tools) working folder
|
|
380
|
+
.ionide/
|
|
381
|
+
|
|
382
|
+
# Fody - auto-generated XML schema
|
|
383
|
+
FodyWeavers.xsd
|
|
384
|
+
|
|
385
|
+
#sandbox
|
|
386
|
+
.dotnet/
|
|
387
|
+
|
|
388
|
+
#nuget
|
|
389
|
+
.nuget/
|
|
390
|
+
|
|
391
|
+
#macos
|
|
392
|
+
.DS_Store
|
|
393
|
+
|
|
394
|
+
#reactjs
|
|
395
|
+
dist/
|
|
396
|
+
# k6 performance test output
|
|
397
|
+
tests/PerformanceTests/reports/*.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Coded Projects
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: api-mapper-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Client SDK for the CodedProjects AI ApiMapper Runtime
|
|
5
|
+
Project-URL: Homepage, https://apimapper.ai
|
|
6
|
+
Author: Coded Projects
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,apimapper,client,llm,mcp,python,sdk,tools
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'all'
|
|
22
|
+
Requires-Dist: langgraph>=0.2.0; extra == 'all'
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'all'
|
|
24
|
+
Provides-Extra: langchain
|
|
25
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'langchain'
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'langchain'
|
|
27
|
+
Provides-Extra: langgraph
|
|
28
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'langgraph'
|
|
29
|
+
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'langgraph'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# api-mapper-client
|
|
34
|
+
|
|
35
|
+
Python SDK for discovering and invoking AI ApiMapper Runtime tools from Python applications and agents.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install api-mapper-client
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Optional integrations:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "api-mapper-client[langchain]"
|
|
47
|
+
pip install "api-mapper-client[langgraph]"
|
|
48
|
+
pip install "api-mapper-client[all]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What it provides
|
|
52
|
+
|
|
53
|
+
- runtime tool discovery,
|
|
54
|
+
- runtime system prompt loading,
|
|
55
|
+
- tool invocation over MCP-compatible HTTP endpoints,
|
|
56
|
+
- API key, delegated bearer token, and OAuth2 client credentials authentication helpers,
|
|
57
|
+
- optional LangChain and LangGraph integration helpers.
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import uuid
|
|
63
|
+
|
|
64
|
+
from api_mapper_client import ApiMapperClient, ApiMapperClientOptions
|
|
65
|
+
from api_mapper_client.auth.api_key import ApiKeyCredentialProvider
|
|
66
|
+
|
|
67
|
+
client = ApiMapperClient(
|
|
68
|
+
ApiMapperClientOptions(
|
|
69
|
+
base_url="https://runtime.example.com",
|
|
70
|
+
tenant_id=uuid.UUID("11111111-1111-1111-1111-111111111111"),
|
|
71
|
+
client_id="my-ai-client",
|
|
72
|
+
credentials=ApiKeyCredentialProvider("replace-with-your-api-key"),
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
tools = await client.get_tools()
|
|
77
|
+
print([tool.name for tool in tools])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# api-mapper-client
|
|
2
|
+
|
|
3
|
+
Python SDK for discovering and invoking AI ApiMapper Runtime tools from Python applications and agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install api-mapper-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Optional integrations:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install "api-mapper-client[langchain]"
|
|
15
|
+
pip install "api-mapper-client[langgraph]"
|
|
16
|
+
pip install "api-mapper-client[all]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What it provides
|
|
20
|
+
|
|
21
|
+
- runtime tool discovery,
|
|
22
|
+
- runtime system prompt loading,
|
|
23
|
+
- tool invocation over MCP-compatible HTTP endpoints,
|
|
24
|
+
- API key, delegated bearer token, and OAuth2 client credentials authentication helpers,
|
|
25
|
+
- optional LangChain and LangGraph integration helpers.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import uuid
|
|
31
|
+
|
|
32
|
+
from api_mapper_client import ApiMapperClient, ApiMapperClientOptions
|
|
33
|
+
from api_mapper_client.auth.api_key import ApiKeyCredentialProvider
|
|
34
|
+
|
|
35
|
+
client = ApiMapperClient(
|
|
36
|
+
ApiMapperClientOptions(
|
|
37
|
+
base_url="https://runtime.example.com",
|
|
38
|
+
tenant_id=uuid.UUID("11111111-1111-1111-1111-111111111111"),
|
|
39
|
+
client_id="my-ai-client",
|
|
40
|
+
credentials=ApiKeyCredentialProvider("replace-with-your-api-key"),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
tools = await client.get_tools()
|
|
45
|
+
print([tool.name for tool in tools])
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .client import ApiMapperClient, ApiMapperClientOptions
|
|
2
|
+
from .auth.api_key import ApiKeyCredentialProvider
|
|
3
|
+
from .auth.delegated_bearer import DelegatedBearerCredentialProvider
|
|
4
|
+
from .auth.oauth2_client_credentials import OAuth2ClientCredentialsProvider, OAuth2ClientCredentialsOptions
|
|
5
|
+
from .contracts import McpTool, McpToolCallResult, McpToolContent
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ApiMapperClient",
|
|
9
|
+
"ApiMapperClientOptions",
|
|
10
|
+
"ApiKeyCredentialProvider",
|
|
11
|
+
"DelegatedBearerCredentialProvider",
|
|
12
|
+
"OAuth2ClientCredentialsProvider",
|
|
13
|
+
"OAuth2ClientCredentialsOptions",
|
|
14
|
+
"McpTool",
|
|
15
|
+
"McpToolCallResult",
|
|
16
|
+
"McpToolContent",
|
|
17
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .base import RuntimeCredentialProvider
|
|
2
|
+
from .api_key import ApiKeyCredentialProvider
|
|
3
|
+
from .delegated_bearer import DelegatedBearerCredentialProvider
|
|
4
|
+
from .oauth2_client_credentials import OAuth2ClientCredentialsProvider, OAuth2ClientCredentialsOptions
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"RuntimeCredentialProvider",
|
|
8
|
+
"ApiKeyCredentialProvider",
|
|
9
|
+
"DelegatedBearerCredentialProvider",
|
|
10
|
+
"OAuth2ClientCredentialsProvider",
|
|
11
|
+
"OAuth2ClientCredentialsOptions",
|
|
12
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .base import RuntimeCredentialProvider
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ApiKeyCredentialProvider(RuntimeCredentialProvider):
|
|
5
|
+
"""Authenticates using a static API key issued from the ApiMapper Portal."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, api_key: str) -> None:
|
|
8
|
+
if not api_key:
|
|
9
|
+
raise ValueError("api_key must not be empty.")
|
|
10
|
+
self._api_key = api_key
|
|
11
|
+
|
|
12
|
+
async def enrich(self, headers: dict[str, str]) -> None:
|
|
13
|
+
headers["X-Api-Key"] = self._api_key
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from collections.abc import Callable, Awaitable
|
|
2
|
+
from .base import RuntimeCredentialProvider
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DelegatedBearerCredentialProvider(RuntimeCredentialProvider):
|
|
6
|
+
"""
|
|
7
|
+
Forwards a Bearer token obtained from a caller-supplied factory.
|
|
8
|
+
The factory is called on every request — no caching is performed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, token_factory: Callable[[], str | Awaitable[str]]) -> None:
|
|
12
|
+
self._factory = token_factory
|
|
13
|
+
|
|
14
|
+
async def enrich(self, headers: dict[str, str]) -> None:
|
|
15
|
+
import asyncio
|
|
16
|
+
result = self._factory()
|
|
17
|
+
token = await result if asyncio.iscoroutine(result) else result
|
|
18
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
import httpx
|
|
4
|
+
from .base import RuntimeCredentialProvider
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class OAuth2ClientCredentialsOptions:
|
|
9
|
+
client_id: str
|
|
10
|
+
client_secret: str
|
|
11
|
+
scope: str
|
|
12
|
+
authority: str | None = None
|
|
13
|
+
token_endpoint: str | None = None
|
|
14
|
+
expiry_buffer_seconds: int = 30
|
|
15
|
+
|
|
16
|
+
def __post_init__(self) -> None:
|
|
17
|
+
if not self.authority and not self.token_endpoint:
|
|
18
|
+
raise ValueError("Either authority or token_endpoint must be set.")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OAuth2ClientCredentialsProvider(RuntimeCredentialProvider):
|
|
22
|
+
"""Authenticates using the OAuth 2.0 Client Credentials flow with automatic token caching."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, opts: OAuth2ClientCredentialsOptions) -> None:
|
|
25
|
+
self._opts = opts
|
|
26
|
+
self._cached_token: str | None = None
|
|
27
|
+
self._expires_at: float = 0.0
|
|
28
|
+
self._resolved_endpoint: str | None = None
|
|
29
|
+
|
|
30
|
+
async def enrich(self, headers: dict[str, str]) -> None:
|
|
31
|
+
token = await self._get_token()
|
|
32
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
33
|
+
|
|
34
|
+
async def _get_token(self) -> str:
|
|
35
|
+
if self._cached_token and time.monotonic() < self._expires_at:
|
|
36
|
+
return self._cached_token
|
|
37
|
+
|
|
38
|
+
endpoint = await self._resolve_endpoint()
|
|
39
|
+
|
|
40
|
+
async with httpx.AsyncClient() as client:
|
|
41
|
+
response = await client.post(
|
|
42
|
+
endpoint,
|
|
43
|
+
data={
|
|
44
|
+
"grant_type": "client_credentials",
|
|
45
|
+
"client_id": self._opts.client_id,
|
|
46
|
+
"client_secret": self._opts.client_secret,
|
|
47
|
+
"scope": self._opts.scope,
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
payload = response.json()
|
|
52
|
+
|
|
53
|
+
self._cached_token = payload["access_token"]
|
|
54
|
+
self._expires_at = time.monotonic() + payload["expires_in"] - self._opts.expiry_buffer_seconds
|
|
55
|
+
return self._cached_token
|
|
56
|
+
|
|
57
|
+
async def _resolve_endpoint(self) -> str:
|
|
58
|
+
if self._resolved_endpoint:
|
|
59
|
+
return self._resolved_endpoint
|
|
60
|
+
|
|
61
|
+
if self._opts.token_endpoint:
|
|
62
|
+
self._resolved_endpoint = self._opts.token_endpoint
|
|
63
|
+
return self._resolved_endpoint
|
|
64
|
+
|
|
65
|
+
discovery_url = f"{self._opts.authority!.rstrip('/')}/.well-known/openid-configuration"
|
|
66
|
+
async with httpx.AsyncClient() as client:
|
|
67
|
+
res = await client.get(discovery_url)
|
|
68
|
+
res.raise_for_status()
|
|
69
|
+
self._resolved_endpoint = res.json()["token_endpoint"]
|
|
70
|
+
|
|
71
|
+
return self._resolved_endpoint
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .auth.base import RuntimeCredentialProvider
|
|
11
|
+
from .contracts import McpTool, McpToolCallResult, McpToolContent
|
|
12
|
+
|
|
13
|
+
MCP_PROTOCOL_VERSION = "2025-03-26"
|
|
14
|
+
JSONRPC_VERSION = "2.0"
|
|
15
|
+
|
|
16
|
+
# Auth header values (Bearer tokens, API keys) are never logged.
|
|
17
|
+
# Tool arguments are not logged to avoid inadvertent PII/secret exposure.
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ApiMapperClientOptions:
|
|
23
|
+
base_url: str
|
|
24
|
+
tenant_id: uuid.UUID
|
|
25
|
+
client_id: str
|
|
26
|
+
credentials: RuntimeCredentialProvider
|
|
27
|
+
system_prompt_resource_uri: str | None = None
|
|
28
|
+
client_name: str = "ApiMapper.Client"
|
|
29
|
+
client_version: str = "1.0.0"
|
|
30
|
+
cache_tools: bool = False
|
|
31
|
+
cache_system_prompt: bool = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ApiMapperClient:
|
|
35
|
+
def __init__(self, opts: ApiMapperClientOptions) -> None:
|
|
36
|
+
self._opts = opts
|
|
37
|
+
mcp_path = f"/runtime/{opts.tenant_id}/{opts.client_id}/mcp"
|
|
38
|
+
self._mcp_url = f"{opts.base_url.rstrip('/')}{mcp_path}"
|
|
39
|
+
self._session_id: str | None = None
|
|
40
|
+
self._initialized = False
|
|
41
|
+
self._cached_tools: list[McpTool] | None = None
|
|
42
|
+
self._cached_prompt: str | None | object = _UNSET
|
|
43
|
+
self._http = httpx.AsyncClient()
|
|
44
|
+
|
|
45
|
+
# ── Public API ────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
async def get_system_prompt(self) -> str | None:
|
|
48
|
+
if not self._opts.system_prompt_resource_uri:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
if self._opts.cache_system_prompt and self._cached_prompt is not _UNSET:
|
|
52
|
+
logger.debug("System prompt returned from cache.")
|
|
53
|
+
return self._cached_prompt # type: ignore[return-value]
|
|
54
|
+
|
|
55
|
+
logger.debug("Fetching system prompt from resource '%s'.", self._opts.system_prompt_resource_uri)
|
|
56
|
+
|
|
57
|
+
result = await self._send("resources/read", {"uri": self._opts.system_prompt_resource_uri})
|
|
58
|
+
contents = (result or {}).get("contents", [])
|
|
59
|
+
text = "\n\n".join(
|
|
60
|
+
c["text"].strip() for c in contents if c.get("text", "").strip()
|
|
61
|
+
)
|
|
62
|
+
self._cached_prompt = text or None
|
|
63
|
+
logger.info("System prompt fetched (%d chars).", len(self._cached_prompt or ""))
|
|
64
|
+
return self._cached_prompt # type: ignore[return-value]
|
|
65
|
+
|
|
66
|
+
async def get_tools(self) -> list[McpTool]:
|
|
67
|
+
if self._opts.cache_tools and self._cached_tools is not None:
|
|
68
|
+
logger.debug("Tool list returned from cache (%d tools).", len(self._cached_tools))
|
|
69
|
+
return self._cached_tools
|
|
70
|
+
|
|
71
|
+
logger.debug("Fetching tool list from Runtime.")
|
|
72
|
+
|
|
73
|
+
tools: list[McpTool] = []
|
|
74
|
+
cursor: str | None = None
|
|
75
|
+
page = 0
|
|
76
|
+
|
|
77
|
+
while True:
|
|
78
|
+
params: dict[str, Any] = {}
|
|
79
|
+
if cursor:
|
|
80
|
+
params["cursor"] = cursor
|
|
81
|
+
result = await self._send("tools/list", params or None)
|
|
82
|
+
if not result:
|
|
83
|
+
logger.warning("tools/list returned None on page %d; stopping pagination.", page)
|
|
84
|
+
break
|
|
85
|
+
for t in result.get("tools", []):
|
|
86
|
+
tools.append(McpTool(
|
|
87
|
+
name=t["name"],
|
|
88
|
+
description=t.get("description"),
|
|
89
|
+
input_schema=t.get("inputSchema", {}),
|
|
90
|
+
))
|
|
91
|
+
cursor = result.get("nextCursor")
|
|
92
|
+
page += 1
|
|
93
|
+
if not cursor:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
self._cached_tools = tools
|
|
97
|
+
logger.info("Loaded %d tools from Runtime.", len(tools))
|
|
98
|
+
return tools
|
|
99
|
+
|
|
100
|
+
async def invoke_tool(self, tool_name: str, arguments: dict[str, Any] | None = None) -> McpToolCallResult | None:
|
|
101
|
+
# Arguments are intentionally not logged to avoid PII/secret exposure.
|
|
102
|
+
logger.debug("Invoking tool '%s'.", tool_name)
|
|
103
|
+
|
|
104
|
+
result = await self._send("tools/call", {"name": tool_name, "arguments": arguments})
|
|
105
|
+
if result is None:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
contents = [
|
|
109
|
+
McpToolContent(type=c.get("type", "text"), text=c.get("text"), data=c.get("data"))
|
|
110
|
+
for c in result.get("content", [])
|
|
111
|
+
]
|
|
112
|
+
call_result = McpToolCallResult(
|
|
113
|
+
content=contents,
|
|
114
|
+
structured_content=result.get("structuredContent"),
|
|
115
|
+
is_error=result.get("isError"),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if call_result.is_error:
|
|
119
|
+
logger.warning("Tool '%s' returned is_error=True.", tool_name)
|
|
120
|
+
else:
|
|
121
|
+
logger.debug("Tool '%s' invoked successfully.", tool_name)
|
|
122
|
+
|
|
123
|
+
return call_result
|
|
124
|
+
|
|
125
|
+
def reset_session(self) -> None:
|
|
126
|
+
self._session_id = None
|
|
127
|
+
self._initialized = False
|
|
128
|
+
self._cached_tools = None
|
|
129
|
+
self._cached_prompt = _UNSET
|
|
130
|
+
logger.info("ApiMapper client session reset.")
|
|
131
|
+
|
|
132
|
+
async def close(self) -> None:
|
|
133
|
+
await self._http.aclose()
|
|
134
|
+
|
|
135
|
+
async def __aenter__(self) -> "ApiMapperClient":
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
async def __aexit__(self, *_: Any) -> None:
|
|
139
|
+
await self.close()
|
|
140
|
+
|
|
141
|
+
# ── MCP internals ─────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
async def _send(self, method: str, params: Any = None) -> dict[str, Any] | None:
|
|
144
|
+
await self._ensure_initialized()
|
|
145
|
+
return await self._send_core(method, params)
|
|
146
|
+
|
|
147
|
+
async def _ensure_initialized(self) -> None:
|
|
148
|
+
if self._initialized:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
logger.debug("Sending MCP initialize handshake.")
|
|
152
|
+
result = await self._send_core("initialize", {
|
|
153
|
+
"protocolVersion": MCP_PROTOCOL_VERSION,
|
|
154
|
+
"capabilities": {},
|
|
155
|
+
"clientInfo": {"name": self._opts.client_name, "version": self._opts.client_version},
|
|
156
|
+
}, skip_init=True)
|
|
157
|
+
if result is None:
|
|
158
|
+
raise RuntimeError("MCP initialize failed.")
|
|
159
|
+
await self._notify("notifications/initialized")
|
|
160
|
+
self._initialized = True
|
|
161
|
+
logger.info("MCP session initialized (session: %s).", self._session_id or "none")
|
|
162
|
+
|
|
163
|
+
async def _send_core(self, method: str, params: Any, skip_init: bool = False) -> dict[str, Any] | None:
|
|
164
|
+
if not skip_init:
|
|
165
|
+
await self._ensure_initialized()
|
|
166
|
+
|
|
167
|
+
# Auth headers are added by the credential provider — their values are never logged.
|
|
168
|
+
headers = await self._build_headers()
|
|
169
|
+
body = {
|
|
170
|
+
"jsonrpc": JSONRPC_VERSION,
|
|
171
|
+
"id": str(uuid.uuid4()),
|
|
172
|
+
"method": method,
|
|
173
|
+
"params": params,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
response = await self._http.post(self._mcp_url, json=body, headers=headers)
|
|
177
|
+
self._capture_session(response)
|
|
178
|
+
|
|
179
|
+
if not response.is_success:
|
|
180
|
+
logger.warning("MCP request '%s' failed with HTTP %d.", method, response.status_code)
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
envelope = response.json()
|
|
184
|
+
if "error" in envelope:
|
|
185
|
+
err = envelope["error"]
|
|
186
|
+
logger.warning(
|
|
187
|
+
"MCP request '%s' returned JSON-RPC error %s: %s.",
|
|
188
|
+
method, err.get("code"), err.get("message"),
|
|
189
|
+
)
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
return envelope.get("result")
|
|
193
|
+
|
|
194
|
+
async def _notify(self, method: str, params: Any = None) -> None:
|
|
195
|
+
headers = await self._build_headers()
|
|
196
|
+
body = {"jsonrpc": JSONRPC_VERSION, "method": method, "params": params}
|
|
197
|
+
response = await self._http.post(self._mcp_url, json=body, headers=headers)
|
|
198
|
+
self._capture_session(response)
|
|
199
|
+
if not response.is_success:
|
|
200
|
+
logger.warning("MCP notification '%s' failed with HTTP %d.", method, response.status_code)
|
|
201
|
+
|
|
202
|
+
async def _build_headers(self) -> dict[str, str]:
|
|
203
|
+
headers: dict[str, str] = {
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
"Accept": "application/json, text/event-stream",
|
|
206
|
+
"MCP-Protocol-Version": MCP_PROTOCOL_VERSION,
|
|
207
|
+
}
|
|
208
|
+
if self._session_id:
|
|
209
|
+
headers["MCP-Session-Id"] = self._session_id
|
|
210
|
+
# Credential provider adds auth headers — values are never logged.
|
|
211
|
+
await self._opts.credentials.enrich(headers)
|
|
212
|
+
return headers
|
|
213
|
+
|
|
214
|
+
def _capture_session(self, response: httpx.Response) -> None:
|
|
215
|
+
sid = response.headers.get("MCP-Session-Id")
|
|
216
|
+
if sid:
|
|
217
|
+
self._session_id = sid
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
_UNSET = object()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class McpToolContent:
|
|
7
|
+
type: str
|
|
8
|
+
text: str | None = None
|
|
9
|
+
data: Any = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class McpTool:
|
|
14
|
+
name: str
|
|
15
|
+
description: str | None
|
|
16
|
+
input_schema: dict[str, Any]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class McpToolCallResult:
|
|
21
|
+
content: list[McpToolContent] = field(default_factory=list)
|
|
22
|
+
structured_content: Any = None
|
|
23
|
+
is_error: bool | None = None
|
|
24
|
+
|
|
25
|
+
def to_text(self) -> str:
|
|
26
|
+
return "\n\n".join(
|
|
27
|
+
c.text.strip() for c in self.content if c.type == "text" and c.text and c.text.strip()
|
|
28
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
4
|
+
from pydantic import BaseModel, Field, create_model
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from api_mapper_client import ApiMapperClient, McpTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _build_pydantic_model(tool: McpTool) -> type[BaseModel]:
|
|
11
|
+
"""Builds a Pydantic model from the tool's JSON Schema properties."""
|
|
12
|
+
props: dict[str, Any] = tool.input_schema.get("properties", {})
|
|
13
|
+
required: list[str] = tool.input_schema.get("required", [])
|
|
14
|
+
fields: dict[str, Any] = {}
|
|
15
|
+
for name, schema in props.items():
|
|
16
|
+
desc = schema.get("description", "")
|
|
17
|
+
default = ... if name in required else None
|
|
18
|
+
fields[name] = (Any, Field(default, description=desc))
|
|
19
|
+
return create_model(f"{tool.name}Args", **fields)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _to_structured_tool(mapper: ApiMapperClient, tool: McpTool) -> StructuredTool:
|
|
23
|
+
model = _build_pydantic_model(tool)
|
|
24
|
+
|
|
25
|
+
async def _run(**kwargs: Any) -> str:
|
|
26
|
+
result = await mapper.invoke_tool(tool.name, kwargs)
|
|
27
|
+
return result.to_text() if result else ""
|
|
28
|
+
|
|
29
|
+
return StructuredTool.from_function(
|
|
30
|
+
coroutine=_run,
|
|
31
|
+
name=tool.name,
|
|
32
|
+
description=tool.description or "",
|
|
33
|
+
args_schema=model,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def create_api_mapper_tools(mapper: ApiMapperClient) -> list[BaseTool]:
|
|
38
|
+
"""Loads all ApiMapper tools and returns them as LangChain StructuredTool instances."""
|
|
39
|
+
tools = await mapper.get_tools()
|
|
40
|
+
return [_to_structured_tool(mapper, t) for t in tools]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ApiMapperToolkit:
|
|
44
|
+
"""Wraps an ApiMapperClient as a LangChain toolkit (lazy-loaded)."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, client: ApiMapperClient) -> None:
|
|
47
|
+
self._client = client
|
|
48
|
+
self._tools: list[BaseTool] | None = None
|
|
49
|
+
|
|
50
|
+
async def get_tools(self) -> list[BaseTool]:
|
|
51
|
+
if self._tools is None:
|
|
52
|
+
self._tools = await create_api_mapper_tools(self._client)
|
|
53
|
+
return self._tools
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from langgraph.prebuilt import ToolNode
|
|
4
|
+
from langchain_core.language_models import BaseChatModel
|
|
5
|
+
|
|
6
|
+
from api_mapper_client import ApiMapperClient
|
|
7
|
+
from api_mapper_langchain import create_api_mapper_tools
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def create_api_mapper_tool_node(client: ApiMapperClient) -> ToolNode:
|
|
11
|
+
"""
|
|
12
|
+
Creates a LangGraph ToolNode backed by ApiMapper tools.
|
|
13
|
+
Place this node in your StateGraph to handle tool call messages.
|
|
14
|
+
"""
|
|
15
|
+
tools = await create_api_mapper_tools(client)
|
|
16
|
+
return ToolNode(tools)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def bind_api_mapper_tools(model: BaseChatModel, client: ApiMapperClient) -> BaseChatModel:
|
|
20
|
+
"""
|
|
21
|
+
Binds ApiMapper tools to a LangChain chat model.
|
|
22
|
+
Returns the model with tools bound — pass to an agent node.
|
|
23
|
+
"""
|
|
24
|
+
tools = await create_api_mapper_tools(client)
|
|
25
|
+
return model.bind_tools(tools)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "api-mapper-client"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Client SDK for the CodedProjects AI ApiMapper Runtime"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Coded Projects" },
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.27.0",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["ai", "mcp", "apimapper", "llm", "tools", "python", "sdk", "client"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
]
|
|
29
|
+
urls = { Homepage = "https://apimapper.ai" }
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
langchain = [
|
|
33
|
+
"langchain-core>=0.3.0",
|
|
34
|
+
"pydantic>=2.0.0",
|
|
35
|
+
]
|
|
36
|
+
langgraph = [
|
|
37
|
+
"langgraph>=0.2.0",
|
|
38
|
+
"langchain-core>=0.3.0",
|
|
39
|
+
"pydantic>=2.0.0",
|
|
40
|
+
]
|
|
41
|
+
all = [
|
|
42
|
+
"langchain-core>=0.3.0",
|
|
43
|
+
"langgraph>=0.2.0",
|
|
44
|
+
"pydantic>=2.0.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build]
|
|
48
|
+
include = [
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"api_mapper_client/**/*.py",
|
|
52
|
+
"api_mapper_langchain/**/*.py",
|
|
53
|
+
"api_mapper_langgraph/**/*.py",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.wheel]
|
|
57
|
+
packages = [
|
|
58
|
+
"api_mapper_client",
|
|
59
|
+
"api_mapper_langchain",
|
|
60
|
+
"api_mapper_langgraph",
|
|
61
|
+
]
|